天天看點

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

在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的服務)

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

程式會進入其提供的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

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結
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執行個體

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

程式經過内部系列調用之後,如圖:

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

DomRpcRouter#invokeRpc

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

最終DomRpcRoutingTable#rpcs中存儲了目前系統所有RPC(Map<SchemaPath, AbstractDOMRpcRoutingTableEntry> rpcs;)

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

索引10即cluster-admin.yang中定義的RPC-get_config_leader

其中RPC實際類型為GlobalDOMRpcRoutingTableEntry

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

而真正的Rpc實作,則是存儲于父類AbstractDOMRpcRoutingTableEntry的impls中

Map<YangInstanceIdentifier, List<DOMRpcImplementation>> impls;

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

而針對RoutedDOMRpcRoutingTableEntry 的映射會先去取YangInstanceIdentifier,根據這個去impls裡尋找RPC

接着邏輯來到BindingDOMRpcImplementationAdapter

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結
ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結
@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。

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

最終完成RPC執行體的執行

補充參數序列化

注意上面使用的RPC入參為空,無法了解到從界面的JSON是如何轉變成最終的YANGBEAN對象的。這裡作下補充:

ODL中提供BindingToNormalizedNodeCodec      
ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

如上圖,提供了将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

以下為整個調用棧

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結
ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

可以發現注冊的最終目的是在DOMRpcRoutingTable中存儲:

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結

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

ODL中RPC接口的restconf化原理原理介紹補充參數序列化RPC注冊總結
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直接路由完成。(但新應用部署後,除非重新開機容器,新應用則無法加載,當然可以補充重新開機邏輯,以實作部署新的應用-自研架構即是如此)