第2部分 啟用遠端過程呼叫RPC
1 第2部分 啟用遠端過程呼叫——讓我們構建一個烤麵包機
烤麵包機樣例的第二部分將會加入一些烤麵包行為,為了完成這個任務,我們將會在toaster yang 資料模型中定義一個RPC(遠端過程呼叫)並且會寫一個實現。
1.1 定義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檔案。
1.2 實現RPC方法
我們已經為呼叫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<Future<?>> currentMakeToastTask = new 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<RpcResult<Void>> cancelToast() {
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.<Void> getRpcResult( true,
77. Collections.<RpcError>emptyList() ) );
78. }
79.
80. @Override
81. public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
82. final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
83.
84. checkStatusAndMakeToast( input, futureResult );
85.
86. return futureResult;
87. }
88.
89. private void checkStatusAndMakeToast( final MakeToastInput input,
90. final SettableFuture<RpcResult<Void>> futureResult ) {
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<Optional<DataObject>> readFuture =
98. tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
99.
100. final ListenableFuture<RpcResult<TransactionStatus>> commitFuture =
101. Futures.transform( readFuture, new AsyncFunction<Optional<DataObject>,
102. RpcResult<TransactionStatus>>() {
103.
104. @Override
105. public ListenableFuture<RpcResult<TransactionStatus>> apply(
106. Optional<DataObject> toasterData ) throws Exception {
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.<TransactionStatus>getRpcResult(
133. false, null, makeToasterInUseError() ) );
134. }
135. } );
136.
137. Futures.addCallback( commitFuture, new FutureCallback<RpcResult<TransactionStatus>>() {
138. @Override
139. public void onSuccess( RpcResult<TransactionStatus> result ) {
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.<Void>getRpcResult( false, null, result.getErrors() ) );
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.<Void> getRpcResult( false, null, Arrays.asList(
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<Void> {
184.
185. final MakeToastInput toastRequest;
186. final SettableFuture<RpcResult<Void>> futureResult;
187.
188. public MakeToastTask( final MakeToastInput toastRequest,
189. final SettableFuture<RpcResult<Void>> futureResult ) {
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<Boolean,Void>() {
220. @Override
221. public Void apply( Boolean result ) {
222.
223. currentMakeToastTask.set( null );
224.
225. LOG.debug("Toast done");
226.
227. futureResult.set( Rpcs.<Void>getRpcResult( true, null,
228. Collections.<RpcError>emptyList() ) );
229.
230. return null;
231. }
232. } );
233. return null;
234. }
235. }
在上面的程式碼中可以看到,我們已經實現了makeToast 和 cancelToast方法,除了AutoCloseable介面的close方法,以確保我們已經完全清理了嵌入的執行緒池。請參考內聯註釋,關注更多細節。
1.3 通過RPC服務註冊OpendaylightToaster
下一步是註冊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<ToasterService> rpcRegistration = getRpcRegistryDependency()
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. <module>
273. <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl">
274. prefix:toaster-provider-impl </type>
275. <name>toaster-provider-impl</name>
276. <rpc-registry>
277. <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-rpc-registry</type>
278. <name>binding-rpc-broker</name>
279. </rpc-registry>
280. ...
281. </module>
Thats it!現在我們已經準備好去部署我們已經更新的bundle並且嘗試我們的makeToast和取消Toaster呼叫。
1.4 通過RestConf呼叫make-toast
這是最後的時刻了,去做美味的小麥麵包了!通過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模型中是預設的,在這裡你還必須給他們賦值。
1.5 通過RestConf呼叫 cancel-toast
如果你不喜歡烤麵包,在執行時你可能想要取消make-toast操作。這可以通過Restconf呼叫 cancel-toast方法進行遠端呼叫:
1. URL => http://localhost:8080/restconf/operations/toaster:cancel-toast
293. HTTP Method => POST
注意:
1.6 看到烤麵包機的狀態更新
看到更新的烤麵包機狀態,呼叫make-toast call(煮熟度為10的最長延遲),然後立即呼叫檢索烤麵包機的執行狀態。您現在應該看到:
1. toaster: {
294. toasterManufacturer: "Opendaylight"
295. toasterModelNumber: "Model 1 - Binding Aware"
296. toasterStatus: "Down"
297. }