第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的值。