烤面包机样例的第二部分将会加入一些烤面包行为,为了完成这个任务,我们将会在toaster yang 数据模型中定义一个RPC(远程过程调用)并且会写一个实现。
编辑现有的toaster.yang文件,我们将会定义2个RPC方法,make-toast 和 cancel-toast。 (add the bold lines under the module toaster heading):
1. module toaster {
2. ...
3. //This defines a Remote Procedure Call (rpc). RPC provide the ability to initiate an action
4. //on the data model. In this case the initating action takes two optional inputs (because default value is defined)
5. //QUESTION: Am I correct that the inputs are optional because they have defaults defined? The REST call doesn't seem to account for this.
6. rpc make-toast {
7. description
8. "Make some toast. The toastDone notification will be sent when the toast is finished.
9. An 'in-use' error will be returned if toast is already being made. A 'resource-denied' error will
10. be returned if the toaster service is disabled.";
11.
12. input {
13. leaf toasterDoneness {
14. type uint32 {
15. range "1 .. 10";
16. }
17. default '5';
18. description
19. "This variable controls how well-done is the ensuing toast. It should be on a scale of 1 to 10.
20. Toast made at 10 generally is considered unfit for human consumption; toast made at 1 is warmed lightly.";
21. }
22.
23. leaf toasterToastType {
24. type identityref {
25. base toast:toast-type;
26. }
27. default 'wheat-bread';
28. description
29. "This variable informs the toaster of the type of material that is being toasted. The toaster uses this information,
30. combined with toasterDoneness, to compute for how long the material must be toasted to achieve the required doneness.";
31. }
32. }
33. } // rpc make-toast
34.
35. // action to cancel making toast - takes no input parameters
36. rpc cancel-toast {
37. description
38. "Stop making toast, if any is being made.
39. A 'resource-denied' error will be returned
40. if the toaster service is disabled.";
41. } // rpc cancel-toast
42. ...
43. }
运行:
1. mvn clean install
将会额外生成以下几个类:
l ToasterService -一个扩展RPC服务的接口并且定义了RPC方法与yang数据模型的对应关系。
l MakeToastInput -定义了一个为调用make-toast提供输入参数的DTO(数据传输对象)接口。
l MakeToastInputBuilder -一个具体类,用来创建MakeToastInput实例。
注意:重要的是,你每次运行mvn clean时,你都会修改yang文件。有一些文件没有被生成,如果他们已经存在,这可能导致不正确的文件生成。每当你改变 .yang文件后,你都应该运行一下 mvn clean,这样将会删除所有已生成的yang文件,通过 mvn-clean-plugin 在common.opendaylight中定义 pom.xml文件。
我们已经为调用RPC定义了数据模型接口——现在我们必须提供实现。我们将修改OpendaylightToaster类去实现新的ToasterService接口(之前仅被生成了)。为简单起见,下面只给出相关的代码:
1. public class OpendaylightToaster implements ToasterService, AutoCloseable {
44.
45. ...
46. private final ExecutorService executor;
47.
48. // The following holds the Future for the current make toast task.
49. // This is used to cancel the current toast.
50. private final AtomicReference
51.
52. public OpendaylightToaster() {
53. executor = Executors.newFixedThreadPool(1);
54. }
55.
56. /**
57. * Implemented from the AutoCloseable interface.
58. */
59. @Override
60. public void close() throws ExecutionException, InterruptedException {
61. // When we close this service we need to shutdown our executor!
62. executor.shutdown();
63.
64. ...
65. }
66.
67. @Override
68. public Future
69.
70. Future> current = currentMakeToastTask.getAndSet( null );
71. if( current != null ) {
72. current.cancel( true );
73. }
74.
75. // Always return success from the cancel toast call.
76. return Futures.immediateFuture( Rpcs.
77. Collections.
78. }
79.
80. @Override
81. public Future
82. final SettableFuture
83.
84. checkStatusAndMakeToast( input, futureResult );
85.
86. return futureResult;
87. }
88.
89. private void checkStatusAndMakeToast( final MakeToastInput input,
90. final SettableFuture
91.
92. // Read the ToasterStatus and, if currently Up, try to write the status to Down.
93. // If that succeeds, then we essentially have an exclusive lock and can proceed
94. // to make toast.
95.
96. final ReadWriteTransaction tx = dataProvider.newReadWriteTransaction();
97. ListenableFuture
98. tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
99.
100. final ListenableFuture
101. Futures.transform( readFuture, new AsyncFunction
102. RpcResult
103.
104. @Override
105. public ListenableFuture
106. Optional
107.
108. ToasterStatus toasterStatus = ToasterStatus.Up;
109. if( toasterData.isPresent() ) {
110. toasterStatus = ((Toaster)toasterData.get()).getToasterStatus();
111. }
112.
113. LOG.debug( "Read toaster status: {}", toasterStatus );
114.
115. if( toasterStatus == ToasterStatus.Up ) {
116.
117. LOG.debug( "Setting Toaster status to Down" );
118.
119. // We're not currently making toast - try to update the status to Down
120. // to indicate we're going to make toast. This acts as a lock to prevent
121. // concurrent toasting.
122. tx.put( LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
123. buildToaster( ToasterStatus.Down ) );
124. return tx.commit();
125. }
126.
127. LOG.debug( "Oops - already making toast!" );
128.
129. // Return an error since we are already making toast. This will get
130. // propagated to the commitFuture below which will interpret the null
131. // TransactionStatus in the RpcResult as an error condition.
132. return Futures.immediateFuture( Rpcs.
133. false, null, makeToasterInUseError() ) );
134. }
135. } );
136.
137. Futures.addCallback( commitFuture, new FutureCallback
138. @Override
139. public void onSuccess( RpcResult
140. if( result.getResult() == TransactionStatus.COMMITED ) {
141.
142. // OK to make toast
143. currentMakeToastTask.set( executor.submit(
144. new MakeToastTask( input, futureResult ) ) );
145. } else {
146.
147. LOG.debug( "Setting error result" );
148.
149. // Either the transaction failed to commit for some reason or, more likely,
150. // the read above returned ToasterStatus.Down. Either way, fail the
151. // futureResult and copy the errors.
152.
153. futureResult.set( Rpcs.
154. }
155. }
156.
157. @Override
158. public void onFailure( Throwable ex ) {
159. if( ex instanceof OptimisticLockFailedException ) {
160.
161. // Another thread is likely trying to make toast simultaneously and updated the
162. // status before us. Try reading the status again - if another make toast is
163. // now in progress, we should get ToasterStatus.Down and fail.
164.
165. LOG.debug( "Got OptimisticLockFailedException - trying again" );
166.
167. checkStatusAndMakeToast( input, futureResult );
168.
169. } else {
170.
171. LOG.error( "Failed to commit Toaster status", ex );
172.
173. // Got some unexpected error so fail.
174. futureResult.set( Rpcs.
175. RpcErrors.getRpcError( null, null, null, ErrorSeverity.ERROR,
176. ex.getMessage(),
177. ErrorType.APPLICATION, ex ) ) ) );
178. }
179. }
180. } );
181. }
182.
183. private class MakeToastTask implements Callable
184.
185. final MakeToastInput toastRequest;
186. final SettableFuture
187.
188. public MakeToastTask( final MakeToastInput toastRequest,
189. final SettableFuture
190. this.toastRequest = toastRequest;
191. this.futureResult = futureResult;
192. }
193.
194. @Override
195. public Void call() {
196. try
197. {
198. // make toast just sleeps for n seconds.
199. long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
200. Thread.sleep(toastRequest.getToasterDoneness());
201. }
202. catch( InterruptedException e ) {
203. LOG.info( "Interrupted while making the toast" );
204. }
205.
206. toastsMade.incrementAndGet();
207.
208. amountOfBreadInStock.getAndDecrement();
209. if( outOfBread() ) {
210. LOG.info( "Toaster is out of bread!" );
211.
212. notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
213. }
214.
215. // Set the Toaster status back to up - this essentially releases the toasting lock.
216. // We can't clear the current toast task nor set the Future result until the
217. // update has been committed so we pass a callback to be notified on completion.
218.
219. setToasterStatusUp( new Function
220. @Override
221. public Void apply( Boolean result ) {
222.
223. currentMakeToastTask.set( null );
224.
225. LOG.debug("Toast done");
226.
227. futureResult.set( Rpcs.
228. Collections.
229.
230. return null;
231. }
232. } );
233. return null;
234. }
235. }
在上面的代码中可以看到,我们已经实现了makeToast 和 cancelToast方法,除了AutoCloseable接口的close方法,以确保我们已经完全清理了嵌入的线程池。请参考内联注释,关注更多细节。
下一步是注册OpendaylightToaster作为RPC调用的提供者。要做到这些我们首先需要为toaster-provider-impl.yang文件中的MD-SAL's RPC注册服务声明一个依赖关系,类似于之前配置data broker服务的过程:
1. //augments the configuration,
236. augment "/config:modules/config:module/config:configuration" {
237. case toaster-provider-impl {
238. when "/config:modules/config:module/config:type = 'toaster-provider-impl'";
239. ...
240.
241. //Wires dependent services into this class - in this case the RPC registry servic
242. container rpc-registry {
243. uses config:service-ref {
244. refine type {
245. mandatory true;
246. config:required-identity mdsal:binding-rpc-registry;
247. }
248. }
249. }
250. }
251. }
重新生成资源。生成的AbstractToasterProviderModule类现在将有一个getRpcRegistryDependency()方法。我们可以访问toasterprovidermodule方法的实现来用RPC注册服务,注册OpenDaylightToaster:
1. @Override
252. public java.lang.AutoCloseable createInstance() {
253. final OpendaylightToaster opendaylightToaster = new OpendaylightToaster();
254.
255. ...
256.
257. final BindingAwareBroker.RpcRegistration
258. .addRpcImplementation(ToasterService.class, opendaylightToaster);
259.
260. final class AutoCloseableToaster implements AutoCloseable {
261.
262. @Override
263. public void close() throws Exception {
264. ...
265. rpcRegistration.close();
266. ...
267. }
268.
269. }
270.
271. return new AutoCloseableToaster();
272. }
最后我们需要为'rpc-registry'到初始配置的XML文件中的toaster-provider-impl module(03-sample-toaster.xml)添加一个依赖关系,和之前配置'data-broker'的过程一样:
1.
273.
274. prefix:toaster-provider-impl
275.
276.
277.
278.
279.
280. ...
281.
Thats it!现在我们已经准备好去部署我们已经更新的bundle并且尝试我们的makeToast和取消Toaster调用。
这是最后的时刻了,去做美味的小麦面包了!通过Restconf调用make-toast你将执行一个HTTP POST去操作URL。
1. HTTP Method => POST
282. URL => http://localhost:8080/restconf/operations/toaster:make-toast
283. Header => Content-Type: application/yang.data+json
284. Body =>
285. {
286. "input" :
287. {
288. "toaster:toasterDoneness" : "10",
289. "toaster:toasterToastType":"wheat-bread"
290. }
291. }
292.
注意:默认和强制性的标志目前还无法实现,所以即使面包类型和煮熟度在yang模型中是默认的,在这里你还必须给他们赋值。
如果你不喜欢烤面包,在运行时你可能想要取消make-toast操作。这可以通过Restconf调用 cancel-toast方法进行远程调用:
1. URL => http://localhost:8080/restconf/operations/toaster:cancel-toast
293. HTTP Method => POST
注意:
看到更新的烤面包机状态,调用make-toast call(煮熟度为10的最长延迟),然后立即调用检索烤面包机的运行状态。您现在应该看到:
1. toaster: {
294. toasterManufacturer: "Opendaylight"
295. toasterModelNumber: "Model 1 - Binding Aware"
296. toasterStatus: "Down"
297. }