在ODL的使用過程, 一直疑惑他是如何将YAGN檔案定義的RPC映射成restconf接口。換句話說,即restconf接口是如何對應指定的RCP的。
原理介紹
下面針以RPC GetConfigLeader為例,揭開其中真相:
以下面的RestConf調用接口,可以通過postman或api-doc直接通路,亦可以通過ODL提供的api-doc通路:
http://localhost:8181/apidoc/explorer/index.html#!/distributed-leader(2016-01-28)/get_config_leader_post_0
從調用棧可以發現其利用了RestconfCompositeWrapper(Jetty提供web容器,用于提供http/https的服務)

程式會進入其提供的invokeRpc方法(所有RPC調用的入口)
@Override
@Deprecated
public NormalizedNodeContext invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
return this.restconf.invokeRpc(identifier, noPayload, uriInfo);
}
這部分使用到了Jetty+Jessery相關web技術,參考web.xml
public class RestconfApplication extends Application {
由RestconfApplication#getSingletons
RestconfApplication提供了XML/JSON/NormalizedNode的轉化器,
.add(NormalizedNodeJsonBodyWriter.class).add(NormalizedNodeXmlBodyWriter.class)
.add(JsonNormalizedNodeBodyReader.class).add(XmlNormalizedNodeBodyReader.class)
序列化這裡存在一個BUG,參考:https://blog.csdn.net/sunquan291/article/details/81912965
并在getSingletons中增加RestconfCompositeWrapper執行個體
程式經過内部系列調用之後,如圖:
DomRpcRouter#invokeRpc
最終DomRpcRoutingTable#rpcs中存儲了目前系統所有RPC(Map<SchemaPath, AbstractDOMRpcRoutingTableEntry> rpcs;)
索引10即cluster-admin.yang中定義的RPC-get_config_leader
其中RPC實際類型為GlobalDOMRpcRoutingTableEntry
而真正的Rpc實作,則是存儲于父類AbstractDOMRpcRoutingTableEntry的impls中
Map<YangInstanceIdentifier, List<DOMRpcImplementation>> impls;
而針對RoutedDOMRpcRoutingTableEntry 的映射會先去取YangInstanceIdentifier,根據這個去impls裡尋找RPC
接着邏輯來到BindingDOMRpcImplementationAdapter
@Override
public Future<RpcResult<?>> invokeOn(RpcService impl, DataObject input) {
try {
return (Future<RpcResult<?>>) handle.invokeExact(impl);
} catch (Throwable e) {
throw Throwables.propagate(e);
}
}
這裡是利用反射以擷取rpc方法進行執行,其使用java.lang.invoker提高反射性能。注意這裡的input對象類型為DataObject。
最終完成RPC執行體的執行
補充參數序列化
注意上面使用的RPC入參為空,無法了解到從界面的JSON是如何轉變成最終的YANGBEAN對象的。這裡作下補充:
ODL中提供BindingToNormalizedNodeCodec
如上圖,提供了将Binding對象與NormalizedNode互轉,這樣加上之前的
add(NormalizedNodeJsonBodyWriter.class).add(NormalizedNodeXmlBodyWriter.class) .add(JsonNormalizedNodeBodyReader.class).add(XmlNormalizedNodeBodyReader.class)
則可以實作了xml/json<---->NormalizedNode<----->Binding對象轉化流程
RPC注冊
要使restconf的url能夠找到對應的rpc,那bundle加載時,相應的rpc存在注冊過程
入口:
RpcProviderRegistry#addRpcImplementation
以下為整個調用棧
可以發現注冊的最終目的是在DOMRpcRoutingTable中存儲:
eg.
ClusterAdmin中的8個RPC進行添加,傳入的implementations是ClusterAdminRpcService
經過第一次轉化,變成了BindingDOMRpcImplementationAdapter
private <S extends RpcService, T extends S> ObjectRegistration<T> register(final Class<S> type, final T implementation, final Collection<YangInstanceIdentifier> rpcContextPaths) {
final Map<SchemaPath, Method> rpcs = codec.getRpcMethodToSchemaPath(type).inverse();
final BindingDOMRpcImplementationAdapter adapter = new BindingDOMRpcImplementationAdapter(codec.getCodecRegistry(), type, rpcs, implementation);
final Set<DOMRpcIdentifier> domRpcs = createDomRpcIdentifiers(rpcs.keySet(), rpcContextPaths);
final DOMRpcImplementationRegistration<?> domReg = domRpcRegistry.registerRpcImplementation(adapter, domRpcs);
return new BindingRpcAdapterRegistration<>(implementation, domReg);
}
經過第二次轉化,變成了BindingDOMRpcImplementationAdapter
private <S extends RpcService, T extends S> ObjectRegistration<T> register(final Class<S> type, final T implementation, final Collection<YangInstanceIdentifier> rpcContextPaths) {
final Map<SchemaPath, Method> rpcs = codec.getRpcMethodToSchemaPath(type).inverse();
final BindingDOMRpcImplementationAdapter adapter = new BindingDOMRpcImplementationAdapter(codec.getCodecRegistry(), type, rpcs, implementation);
final Set<DOMRpcIdentifier> domRpcs = createDomRpcIdentifiers(rpcs.keySet(), rpcContextPaths);
final DOMRpcImplementationRegistration<?> domReg = domRpcRegistry.registerRpcImplementation(adapter, domRpcs);
return new BindingRpcAdapterRegistration<>(implementation, domReg);
}
domRpcRegistry注冊傳回DOMRpcImplementationRegistration,最後包裝成BindingRpcAdapterRegistration
public <T extends RpcService> RpcRegistration<T> addRpcImplementation(final Class<T> type, final T impl)
throws IllegalStateException {
final ObjectRegistration<T> reg = providerAdapter.registerRpcImplementation(type, impl);
return new DelegatedRootRpcRegistration<>(type,reg);
}
再經封裝
final class DelegatedRootRpcRegistration<T extends RpcService> implements RpcRegistration<T>
最後作為傳回值傳回
總結
利用web容器開發規定統一的入口,依據傳入的URL+參數,利用反射尋找對應的執行RPC(應用可以動态加載,入口永遠一緻,自然也就有了動态加載特性)
而通過常用的Restconf将URL綁定至方法上的做法,在通路URL入口,則由Jetty直接路由完成。(但新應用部署後,除非重新開機容器,新應用則無法加載,當然可以補充重新開機邏輯,以實作部署新的應用-自研架構即是如此)