1. 程式人生 > >第2部分 啟用遠端過程呼叫RPC

第2部分 啟用遠端過程呼叫RPC

1 第2部分 啟用遠端過程呼叫——讓我們構建一個烤麵包機

烤麵包機樣例的第二部分將會加入一些烤麵包行為,為了完成這個任務,我們將會在toaster yang 資料模型中定義一個RPC(遠端過程呼叫)並且會寫一個實現。

1.1 定義yang RPC

編輯現有的toaster.yang檔案,我們將會定義2RPC方法,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 module03-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.   }