第5部分:爲ToasterService添加一個消費者——讓我們做早餐!
我們已經看到了如何使用RestConf訪問ToasterService RPC方法。在本節中,我們將展示如何從內部控制器以編程方式訪問ToasterService。
我們將創建一個名爲KitchenService的新服務,它提供了一個方法來做早餐(這是位於sample-toaster-consumer項目中)。此服務將訪問ToasterService提供早餐的烤麪包。
KitchenService定義了一個更高級別的服務爲做一個完整的早餐。這個很好地展示了“service chaining”,消費者的一個或多個服務也是另一個服務提供者。這個例子只會調用到‘toast’服務,但可以看到,它可以擴展到eggs”服務也可以是添加一個“coffee”服務等。
1.1 定義KitchenService接口
爲了簡便起見,我們將手工編寫KitchenService數據模型和接口,而不是在yang文件中定義。在真正kitchenService模型中您可能想在yang文件中定義kitchenService,去獲得由MDSAL提供的自動生成類和開箱即用的功能的好處。對於這個示例,我們定義一個枚舉類型和接口的java文件在src /main/ java下,在包org.opendaylight.controller.sample.kitchen.api中。
1. //EggsType.java
1. public enum EggsType {
2. SCRAMBLED,
3. OVER_EASY,
4. POACHED
5. }
1. //KitchenService.java
6. public interface KitchenService {
7.
8. Future<RpcResult<Void>> makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness );
9.
10. }
我們的早餐只包括簡單的雞蛋和烤麪包——一個完整的早餐也包括燻肉或香腸和咖啡。早餐雞蛋、肉、咖啡等也可以單獨的分配不同的數據模型與相應的服務,如ToasterService——我們把它留給讀者作爲練習。
1.2 定義KitchenService實現
接下來,我們創建一個類KitchenServiceImp,來實現接口和訪問的ToasterService去烤麪包:
1. public class KitchenServiceImpl implements KitchenService {
11.
12. private static final Logger log = LoggerFactory.getLogger( KitchenServiceImpl.class );
13.
14. private final ToasterService toaster;
15.
16. public KitchenServiceImpl(ToasterService toaster) {
17. this.toaster = toaster;
18. }
19.
20. @Override
21. public Future<RpcResult<Void>> makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness ) {
22.
23. // Call makeToast and use JdkFutureAdapters to convert the Future to a ListenableFuture,
24. // The OpendaylightToaster impl already returns a ListenableFuture so the conversion is
25. // actually a no-op.
26.
27. ListenableFuture<RpcResult<Void>> makeToastFuture = JdkFutureAdapters.listenInPoolThread(
28. makeToast( toastType, toastDoneness ), executor );
29.
30. ListenableFuture<RpcResult<Void>> makeEggsFuture = makeEggs( eggsType );
31.
32. // Combine the 2 ListenableFutures into 1 containing a list of RpcResults.
33.
34. ListenableFuture<List<RpcResult<Void>>> combinedFutures =
35. Futures.allAsList( ImmutableList.of( makeToastFuture, makeEggsFuture ) );
36.
37. // Then transform the RpcResults into 1.
38.
39. return Futures.transform( combinedFutures,
40. new AsyncFunction<List<RpcResult<Void>>,RpcResult<Void>>() {
41. @Override
42. public ListenableFuture<RpcResult<Void>> apply( List<RpcResult<Void>> results )
43. throws Exception {
44. boolean atLeastOneSucceeded = false;
45. Builder<RpcError> errorList = ImmutableList.builder();
46. for( RpcResult<Void> result: results ) {
47. if( result.isSuccessful() ) {
48. atLeastOneSucceeded = true;
49. }
50.
51. if( result.getErrors() != null ) {
52. errorList.addAll( result.getErrors() );
53. }
54. }
55.
56. return Futures.immediateFuture(
57. Rpcs.<Void> getRpcResult( atLeastOneSucceeded, errorList.build() ) );
58. }
59. } );
60. }
61.
62. private ListenableFuture<RpcResult<Void>> makeEggs( EggsType eggsType ) {
63.
64. return executor.submit( new Callable<RpcResult<Void>>() {
65.
66. @Override
67. public RpcResult<Void> call() throws Exception {
68.
69. // We don't actually do anything here - just return a successful result.
70. return Rpcs.<Void> getRpcResult( true, Collections.<RpcError>emptyList() );
71. }
72. } );
73. }
74.
75. private Future<RpcResult<Void>> makeToast( Class<? extends ToastType> toastType,
76. int toastDoneness ) {
77. // Access the ToasterService to make the toast.
78.
79. MakeToastInput toastInput = new MakeToastInputBuilder()
80. .setToasterDoneness( (long) toastDoneness )
81. .setToasterToastType( toastType )
82. .build();
83.
84. return toaster.makeToast( toastInput );
85. }
86. }
1.3 連接KitchenService實現
類似於烤麪包機供應者服務,在yang和提供初始配置xml中我們將描述一個廚房服務實現。因此MDSAL可以把這些連接起來。
定義廚房服務
在kitchen-service-impl.yang中我們將定義廚房服務實現及其依賴項:
1. module kitchen-service-impl {
87.
88. yang-version 1;
89. namespace "urn:opendaylight:params:xml:ns:yang:controller:config:kitchen-service:impl";
90. prefix "kitchen-service-impl";
91.
92. import config { prefix config; revision-date 2013-04-05; }
93. import rpc-context { prefix rpcx; revision-date 2013-06-17; }
94.
95. import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; }
96.
97. description
98. "This module contains the base YANG definitions for
99. kitchen-service impl implementation.";
100.
101. revision "2014-01-31" {
102. description
103. "Initial revision.";
104. }
105.
106. // This is the definition of kitchen service interface identity.
107. identity kitchen-service {
108. base "config:service-type";
109. config:java-class "org.opendaylight.controller.sample.kitchen.api.KitchenService";
110. }
111.
112. // This is the definition of kitchen service implementation module identity.
113. identity kitchen-service-impl {
114. base config:module-type;
115. config:provided-service kitchen-service;
116. config:java-name-prefix KitchenService;
117. }
118.
119. augment "/config:modules/config:module/config:configuration" {
120. case kitchen-service-impl {
121. when "/config:modules/config:module/config:type = 'kitchen-service-impl'";
122.
123. container rpc-registry {
124. uses config:service-ref {
125. refine type {
126. mandatory true;
127. config:required-identity mdsal:binding-rpc-registry;
128. }
129. }
130. }
131. }
132. }
133. }
這類似於toaster-provider-impl yang,除了我們也定義一個kitchen-service 服務類型的身份,這爲廚房服務接口定義了一個全局標識符,並且可以被引用。config:java-class性能指定KitchenService java接口。
這kitchen-service的身份將被用於配置子系統去通知提供的服務實例kitchen-service-impl模塊作爲OSGi服務KitchenService java接口。
實現kitchenservicemodule
在運行'mvn clean install'之後,幾個源文件將生成類似於the toaster provider,我們只需要修改KitchenServiceModule.createInstance()方法來實例化KitchenServiceImpl實例並且連接他:
1. @Override
134. public java.lang.AutoCloseable createInstance() {
135. ToasterService toasterService = getRpcRegistryDependency().getRpcService(ToasterService.class);
136.
137. final KitchenServiceImpl kitchenService = new KitchenServiceImpl(toasterService);
138.
139. final class AutoCloseableKitchenService implements KitchenService, AutoCloseable {
140.
141. @Override
142. public void close() throws Exception {
143. }
144.
145. @Override
146. public Future<RpcResult<Void>> makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness ) {
147. return kitchenService.makeBreakfast( eggs, toast, toastDoneness );
148. }
149. }
150.
151. AutoCloseable ret = new AutoCloseableKitchenService();
152. return ret;
153. }
因爲我們指定的爲在kitchen-service-impl.yang中的廚房服務實現模塊提供服務,我們必須返回一個AutoCloseable實例並且也實現了KitchenService接口。否則這將導致一個失敗的配置子系統。
定義初始配置
最後,在初始配置xml創建之前把廚房服務和模塊定義添加到其中:
1. <snapshot>
154. <configuration>
155.
156. <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
157. ...
158. <module>
159. <type xmlns:kitchen="urn:opendaylight:params:xml:ns:yang:controller:config:kitchen-service:impl">
160. kitchen:kitchen-service-impl
161. </type>
162. <name>kitchen-service-impl</name>
163.
164.
165. <rpc-registry>
166. <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-rpc-registry</type>
167. <name>binding-rpc-broker</name>
168. </rpc-registry>
169. </module>
170. </modules>
171. <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
172. <service>
173. <type xmlns:kitchen="urn:opendaylight:params:xml:ns:yang:controller:config:kitchen-service:impl">
174. kitchen:kitchen-service
175. </type>
176. <instance>
177. <name>kitchen-service</name>
178. <provider>/modules/module[type='kitchen-service-impl'][name='kitchen-service-impl']</provider>
179. </instance>
180. </service>
181. </services>
182.
183. </configuration>
184.
185. <required-capabilities>
186. <capability>urn:opendaylight:params:xml:ns:yang:controller:config:kitchen-service:impl?module=kitchen-service-impl&revision=2014-01-31</capability>
187. <capability>urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl?module=toaster-provider-impl&revision=2014-01-31</capability>
188. </required-capabilities>
189. </snapshot>
kitchen-service-impl模塊定義類似於toaster-provider-impl模塊概述。
我們還爲kitchen-service接口定義了一個服務入口,用來告知config subsystem去通知OSGI服務。這個type元素指的是完整的kitchen-service標識並且是指定服務的接口類型。Instance元素指定服務實例信息。Name元素指定一個獨一無二的服務名稱,provider元素指定的路徑形式/modules/module/name來定位kitchen-service-impl模塊,通過模塊名稱提供了服務實例。在運行時,實際的服務實例被實例化,並被插入在config的/services/service/ hierarchy結點下並通知OSGI。
1.4 添加JMX RPC做早餐
在這一點上,如果我們部署kitchen服務我們不能通過restconf去訪問它,因爲我們沒有爲它定義一個yang數據模型。據推測,真正的服務,會有java客戶去消耗它。取而代之的是我們可以利用JMX訓練kitchen服務來做早餐。
通過JMX MD-SAL也支持RPC調用。我們只是在yang中簡單定義了RPC並且通過augmentation把它與config:state綁定起來,就像我們之前爲toaster provider中的clearToastsMade RPC做的一樣。
我們將會爲kitchen-service-impl.yang添加一個make-scrambled-with-wheat RPC定義。這個調用不接受輸入和hard-codes。
1. augment "/config:modules/config:module/config:state" {
190. case kitchen-service-impl {
191. when "/config:modules/config:module/config:type = 'kitchen-service-impl'";
192.
193. rpcx:rpc-context-instance "make-scrambled-with-wheat-rpc";
194. }
195. }
196.
197. identity make-scrambled-with-wheat-rpc;
198.
199. rpc make-scrambled-with-wheat {
200. description
201. "Shortcut JMX call to make breakfast with scrambled eggs and wheat toast for testing.";
202.
203. input {
204. uses rpcx:rpc-context-ref {
205. refine context-instance {
206. rpcx:rpc-context-instance make-scrambled-with-wheat-rpc;
207. }
208. }
209. }
210. output {
211. leaf result {
212. type boolean;
213. }
214. }
215. }
重新生成源後,修改KitchenServiceImpl 實現生成的接口KitchenServiceRuntimeMXBean,它定義了makeScrambledWithWheat()方法。
1. @Override
216. public Boolean makeScrambledWithWheat() {
217. try {
218. // This call has to block since we must return a result to the JMX client.
219. RpcResult<Void> result = makeBreakfast( EggsType.SCRAMBLED, WheatBread.class, 2 ).get();
220. if( result.isSuccessful() ) {
221. log.info( "makeBreakfast succeeded" );
222. } else {
223. log.warn( "makeBreakfast failed: " + result.getErrors() );
224. }
225.
226. return result.isSuccessful();
227.
228. } catch( InterruptedException | ExecutionException e ) {
229. log.warn( "An error occurred while maing breakfast: " + e );
230. }
231.
232. return Boolean.FALSE;
233. }
接下來,修改KitchenServiceModule.createInstance()來使用JMX註冊KitchenService,然後在AutoCloseable wrapper內關閉它。
1. final KitchenServiceRuntimeRegistration runtimeReg =
234. getRootRuntimeBeanRegistratorWrapper().register( kitchenService );
235. ...
236. final class AutoCloseableKitchenService implements AutoCloseable {
237. @Override
238. public void close() throws Exception {
239. ...
240. runtimeReg.close();
241. }
242. }
243. ...
1.5 通過JMX做早餐
我們可以通過JConsole來訪問kitchen-service-impl MBean,正如我們前面對toaster-service-impl MBean所做的一樣。
l 導航到MBeans選項卡。
l 擴展org.opendaylight.controller->RuntimeBean->kitchen-service-impl->kitchen-service-imp->Operations結點。
l 點擊makeScrambledWithWheat按鈕。
l 來驗證實際麪包,擴展org.opendaylight.controller->RuntimeBean->toaster-provider-impl->toaster-provider-imp ->Attributes並檢查ToastsMade的值。