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

上一篇内容

一、实现服务--mytoaster-provider(核心内容)

1、在目录demo2中增加mytoaster-provider目录以及子目录、文件:

mytoaster-provider

├── pom.xml

└── src

    └── main

        ├── java

        └── resources

2Pom文件主要修改内容(参考sample)

  <groupId>org.opendaylight.controller.demo2</groupId>
  <artifactId>mytoaster-provider</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>bundle</packaging>
 
  <dependencies>
   <dependency><!-- mytoaster-provide需要依赖mytoaster定义的服务 -->
      <groupId>${project.groupId}</groupId>
      <artifactId>mytoaster</artifactId>
      <version>${project.version}</version>
   </dependency>
   ....          
  </dependencies>

3、增加源码文件,org/opendaylight/controller/demo2/mytoaster/provider/MyOpenDaylightToaster.java

在开始写代码前介绍一下在java1.7新引入AutoCloseable接口。在java1.7之前释放资源,比如关闭文件句柄,一般写道try-catch中的finally中,但如果在finally中出现异常,那么这个资源就无法回收了。因此在java1.7中增加了AutoCloseable接口,以解决这种问题,用于在最后回收资源,感觉像C++中的析构函数。(但是AutoCloseable出现异常呢?等待高手指点

我们知道对于OSGi框架最小调度单元是bundle,因此我们先现实mytoaster-provider这个bundle,使其能够在odl中跑起来。为了实现这个功能,需要实现以下三步骤:

1AutoCloseable接口。

2) 注册bundle。在碳版本中,bundle的注册是通过blueprint方式,具体blueprint使用可以参考如下两个地址:IBM开发者社区 Opendaylight官方社区

3Feature定义。

具体代码如下:

public class MyOpendaylightToaster implements MytoasterService,AutoCloseable {
    private static final InstanceIdentifier<Mytoaster> TOASTER_IID = InstanceIdentifier.builder(Mytoaster.class).build();
    private static final MyDisplayString TOASTER_MANUFACTURER = new MyDisplayString("Opendaylight");
    private static final MyDisplayString TOASTER_MODEL_NUMBER = new MyDisplayString("Model 1 - Binding Aware");
    private DataBroker dataBroker;

    public void setDataBroker(DataBroker dataBroker) {
        this.dataBroker = dataBroker;
    }
    public MyOpendaylightToaster() {
    }
    /**
     * 初始化Toaster 创建一个toaster保存到datastore中
     */
    public void init() {
        setToasterStatusIdle(null);
    }

    /**
     * 关闭服务 回调函数
     * @throws Exception
     */
    public void close() throws Exception {
        if (dataBroker != null) {
            //将toaster从datastore中删除
            WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
            tx.delete(LogicalDatastoreType.OPERATIONAL, TOASTER_IID);
            Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
                @Override
                public void onSuccess(@Nullable Void aVoid) {
                    System.out.println("Delete MyToaster From Datastore Success.");
                }
                @Override
                public void onFailure(Throwable throwable) {
                    System.out.println("Delete MyToaster From Datastore Failed.");
                }
            });
        }
    }

    /**
     * 创建一个Mytoaster服务
     * @param defaultStatus -- 默认状态 idle
     * @return 返回一个Mytoaster实例
     */
    private Mytoaster createToaster(ToasterStatus defaultStatus) {
        MytoasterBuilder builder = new MytoasterBuilder();
        builder.setToasterManufacturer(TOASTER_MANUFACTURER);
        builder.setToasterModelNumber(TOASTER_MODEL_NUMBER);
        builder.setToasterStatus(defaultStatus);
        return builder.build();
    }
    /**
     * 创建一个Toaster并且设置成IDLE态
     * @param resultCallback -- 回调函数
     */
    private void setToasterStatusIdle(final Function<Boolean, Void> resultCallback) {
        //调用datastore接口 创建一个可写事务 
        //将新创建的Toaster保存到dataStore中
        WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
        tx.put(LogicalDatastoreType.OPERATIONAL, TOASTER_IID, createToaster(ToasterStatus.Idle));
        //提交事务 一般放到一个FUTURE中,如果设置了回调函数 则将设置的结果通过回调函数返回
        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
            @Override
            public void onSuccess(@Nullable Void result) {
                notifyCallback(true);
            }
            @Override
            public void onFailure(Throwable throwable) {
                notifyCallback(false);
            }
            private void notifyCallback(Boolean result) {
                if (resultCallback != null) {
                    resultCallback.apply(result);
                }
            }
        });
    }

 注册bundle,在resources目录中创建org/opendaylight/blueprint/mytoaster-provider.xml,具体文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 此处的odl是 opendaylight对blueprint的扩展 扩展了一些自己特有的 具体内容可以参考推荐的两个网址 -->
<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">
    <!-- 引入dataBroker服务 这样才能操作datastroe -->
    <reference id="dataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"/>
    <!-- 通过bean创建一个MyOpendaylightToaster实例并注册到mdsal中 -->
    <bean id="mytoaster" class="org.opendaylight.controller.demo2.mytoaster.provider.MyOpendaylightToaster"
            init-method="init" destroy-method="close">
        <property name="dataBroker" ref="dataBroker"/>
    </bean>
</blueprint>

 经过上面两步骤,就可以完成了bundle基本功能了,比之前的配置子系统要简单很多!!进入mytoaster-provider,进行编译,编译过程中应该没有任何问题,接下来需要设置feature

设置feature需要做一下修改:

1) Feature定义是在目录controller/features,我们的mytoaster是定义在mdsal中,因此进入controller/features/mdsal增加我们的feature。修改mdsal目录中pom文件,增加<module>odl-mytoaster</module>

2) 由于碳版本中karaf升级到4.0版本(碳版本是过渡版本)feature定义方式存在两种方式。在karaf4下面需要将各个feature独立定义在以俄国目录中,例如是odl-toaster目录。碳之前版本是在mdsal/feature/src/main/features.xml文件中定义。由于对karaf4.0不是很了解,这里只介绍旧方式,即在features.xml文件中定义。增加features定义:

mdsal/features-mdsal/pom.xml文件中增加依赖:

<!-- mytoaster -->
<dependency>
  <groupId>org.opendaylight.controller.demo2</groupId>
  <artifactId>mytoaster</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.opendaylight.controller.demo2</groupId>
  <artifactId>mytoaster-provider</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

mdsal/features-mdsal/src/main/features/features.xml文件中增加feature定义:

<feature name='odl-mytoaster' version='${project.version}' description="OpenDaylight :: MyToaster">
    <feature version='${yangtools.version}'>odl-yangtools-common</feature>
    <feature version='${mdsal.version}'>odl-mdsal-binding-runtime</feature>
    <feature version='${project.version}'>odl-mdsal-broker</feature>
    <bundle>mvn:org.opendaylight.controller.demo2/mytoaster/{{VERSION}}</bundle>    <bundle>mvn:org.opendaylight.controller.demo2/mytoaster-provider/{{VERSION}}</bundle>
</feature>

以上内容就是feature的定义,在mdsal目录中进行编译应该可以正常编译出来。虽然能编译出来但是还不能运行,因为从github下载controller代码,并没有依赖上restconf,因此需要作如下修改:

3) 修改controller/karaf/opendaylight-karaf目录中的pom文件,增加如下内容:

<dependency>
  <groupId>org.opendaylight.netconf</groupId>
  <artifactId>features-netconf</artifactId>
  <version>1.2.2-SNAPSHOT</version>
  <classifier>features</classifier>
  <type>xml</type>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.opendaylight.netconf</groupId>
  <artifactId>features-netconf-connector</artifactId>
  <version>1.2.2-SNAPSHOT</version>
  <classifier>features</classifier>
  <type>xml</type>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.opendaylight.netconf</groupId>
  <artifactId>features-restconf</artifactId>
  <version>1.5.2-SNAPSHOT</version>
  <classifier>features</classifier>
  <type>xml</type>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.opendaylight.netconf</groupId>
  <artifactId>features-yanglib</artifactId>
  <version>1.2.2-SNAPSHOT</version>
  <classifier>features</classifier>
  <type>xml</type>
  <scope>runtime</scope>
</dependency>

经过以上内容修改,把修改的模块重新编译一次,应该就能运行起来了。至此修改编译的目录以及顺序如下:

demo2/mytoasterdemo2/mytoaster-providerfeatures/mdsalkaraf/opendaylight-karaf

编译完成之后,启动karaf,如下:

opendaylight-user@root>
opendaylight-user@root>
opendaylight-user@root>
opendaylight-user@root>feature:install odl-restconf-all
opendaylight-user@root>feature:install odl-mytoaster
opendaylight-user@root>bundle:list | grep toaster
284 | Active   |  80 | 0.0.1.SNAPSHOT                      | mytoaster                                                          
285 | Active   |  80 | 0.0.1.SNAPSHOT                      | mytoaster-provider                                                 
opendaylight-user@root>

我们是postman获取一下toaster基本信息,如何返回和下图一致,则说明正确:

Url:http://localhost:8181/restconf/operational/mytoaster:mytoaster,用户名和密码都是admin

 至此服务的注册已经完成,接下来就是要是服务。我们都知道odl采用rpc的方式提供服务,因此我们要把yang模型中定义的所有rpc都要实现。在MyOpendaylightToaster实现MytoasterService接口,并且添加rpc的实现如下(只显示修改内容):

public class MyOpendaylightToaster implements MytoasterService,AutoCloseable {
    ...
    private static final int MAX_TRY_COUNT = 3;//发生异常时最大尝试次数
    private final ExecutorService executor;
    private final AtomicReference<Future<?>> currentToastTask = new AtomicReference<>();
    ..
    public MyOpendaylightToaster() {
        //只有一个线程 因为一个面包机只能烤完一个才能烤下一个
        executor = Executors.newFixedThreadPool(1);
}
    public void close() throws Exception {
        executor.shutdown();//关闭线程池否则服务无法正常退出
    ...
}
@Override
    public Future<RpcResult<MakeToastOutput>> makeToast(MakeToastInput input) {
        final SettableFuture<RpcResult<MakeToastOutput>> futureResult = SettableFuture.create();
    //检查toaster状态并且制作面包
        checkStatusAndMakeToast(input, MAX_TRY_COUNT, futureResult);
        return futureResult;
    }
    @Override
    public Future<RpcResult<Void>> cancelToast() {
        Future<?> current = currentToastTask.getAndSet(null);
        if (current != null) {
            current.cancel(true);
        }
        // Always return success from the cancel toast call
        return Futures.immediateFuture(RpcResultBuilder.<Void> success().build());
    }
private void checkStatusAndMakeToast(final MakeToastInput input, final int tries,
                                        final SettableFuture<RpcResult<MakeToastOutput>> futureResult) {
    //在系统启动的时候 创建了一个toaster实例并保存在datastore中 我们要先判断这个toaster的状态:
    //Idle状态:继续后续流程 制作面包
    //Work状态:直接返回 提示用户正忙,稍后尝试
    ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
    //此处的Optional导入的包是com.google.common.base.Optional;
    ListenableFuture<Optional<Mytoaster>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL, TOASTER_IID);
    //获取datastore中Toaster的状态
    final ListenableFuture<Void> commitFuture = Futures.transform(readFuture,
            (AsyncFunction<Optional<Mytoaster>, Void>) mytoasterOptional -> {
                ToasterStatus status = ToasterStatus.Idle;
                if (mytoasterOptional.isPresent()) {
                    status = mytoasterOptional.get().getToasterStatus();
                }
                if (status == ToasterStatus.Idle) {
                    tx.put(LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
                            createToaster(ToasterStatus.Work));
                    return tx.submit();
                } else {
                    return Futures.immediateFailedFuture(new TransactionCommitFailedException(
                            "", RpcResultBuilder.newError(RpcError.ErrorType.APPLICATION, "Error", "" +
                            "Read Data Is Error.")
                    ));
                }
            });
    //异步等待结果
    Futures.addCallback(commitFuture, new FutureCallback<Void>() {
        @Override
        public void onSuccess(@Nullable Void aVoid) {
            //表示可以制作面包  现实生活中烤面包是需要一定时间的 而且烤成功后会提醒 所以就餐人员可以
            //作其他事情,不必一直等在面包机钱买年  因此我们把烤面包过程交给一个线程 然后立即响应成功
            currentToastTask.set(executor.submit(new MakeToastTask(input)));
            futureResult.set(RpcResultBuilder.success(makeToastOutput("Please Wait A Moment"))
                    .build());
        }

        @Override
        public void onFailure(Throwable ex) {//表示不能制作面包
            if (ex instanceof OptimisticLockFailedException) {
                //如果是加锁失败则进行尝试
                if (tries - 1 > 0) {
                    checkStatusAndMakeToast(input, tries - 1, futureResult);
                } else {
                    futureResult.set(RpcResultBuilder.<MakeToastOutput>failed().withResult(makeToastOutput("couldn't Make Toaster!"))
                            .build());
                }
            } else if (ex instanceof TransactionCommitFailedException) {
                futureResult.set(RpcResultBuilder.<MakeToastOutput>failed().withResult(makeToastOutput("couldn't Make Toaster!"))
                        .build());
            } else {
                futureResult.set(RpcResultBuilder.<MakeToastOutput>failed().withResult(makeToastOutput("couldn't Make Toaster!"))
                        .build());
            }
        }
    });
}
//内部类 实现callable接口 供线程池使用
    private class MakeToastTask implements Callable<Void> {
        final MakeToastInput toastRequest;
        public MakeToastTask(final MakeToastInput toastRequest) {
            this.toastRequest = toastRequest;
        }
        @Override
        public Void call() throws Exception {
            try {
                // 睡眠15秒 表示制作面包
                Thread.sleep(15);
                System.out.println("Break is out!!Please take it");
                //通知烤面包成功 -- notification 后面实现
            } catch (InterruptedException e) {
                System.out.println("Interrupted while making the toast");
                //通知烤面包失败 -- notification
            } finally {
                //制作完面包 要把toaster状态改成idle状态 以便其他人可以继续烤面包
                setToasterStatusIdle(result -> {
                        currentToastTask.set(null);
                        return null;
                    });
            }
            return null;
        }
    }
}

到目前为止,rpc的主要服务均以实现,但是还存在一个问题,我们的rpc已经实现了,但是如何让mdsal知道这个rpc存在呢?答案是:通过blueprint进行注册。

只需要在mytoaster-provider.xml文件中增加: <odl:rpc-implementation ref="mytoaster"/>

接下来我们来验证结果:

Urlhttp://localhost:8080/restconf/operations/mytoaster:make-toast 


可以看到Break is out!!Please take it 是在15秒之后打印出来的,但是返回结果立即返回并且是就餐者请稍等片刻。这样我们就实现了一个简单rpc服务以及调用。







  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值