接上一篇内容
一、实现服务--mytoaster-provider(核心内容)
1、在目录demo2中增加mytoaster-provider目录以及子目录、文件:
mytoaster-provider
├── pom.xml
└── src
└── main
├── java
└── resources
2、Pom文件主要修改内容(参考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中跑起来。为了实现这个功能,需要实现以下三步骤:
1)AutoCloseable接口。
2) 注册bundle。在碳版本中,bundle的注册是通过blueprint方式,具体blueprint使用可以参考如下两个地址:IBM开发者社区 和Opendaylight官方社区。
3)Feature定义。
具体代码如下:
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/mytoaster,demo2/mytoaster-provider,features/mdsal,karaf/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"/>。
接下来我们来验证结果:
Url:http://localhost:8080/restconf/operations/mytoaster:make-toast
可以看到Break is out!!Please take it 是在15秒之后打印出来的,但是返回结果立即返回并且是就餐者请稍等片刻。这样我们就实现了一个简单rpc服务以及调用。