作者简介:陈卓文,国内某游戏公司私有云团队开发者,主要从事SDN/NFV开发。
前面一系列文章,我们深入探讨了:“OFP启动”、“Switch从上线到下线在OFP的整个生命周期”。本文,我们展开可能是开发者接触最多的问题:如何下发流表,及其在OFP中是如何实现;OFP是如何封装对底层Switch的操作;
附:
第一篇:(一)ODL OpenflowPlugin启动流程源码分析
第二篇:(二)ODL Openflowplugin Switch连上控制器Handshake过程源码分析
第三篇:(三)ODL Openflowplugin Switch生命周期对象ContextChain创建源码分析
第四篇:(四)ODL Openflowplugin Master选举及Context服务实例化源码分析
第五篇:(五)ODL Openflowplugin Mastership及ReconciliationFramework源码分析
第六篇:(六)ODL Openflowplugin 控制器成为SLAVE过程源码分析
第七篇:(七)ODL Openflowplugin Switch断开控制器下线源码分析
OFP对底层Switch的封装
Openflowplugin将对底层Switch的操作都定义为RPC(YANG),在openflowplugin/model
目录下存储定义的RPC。比如,在前文提及的Master/Slave选举,其最终都会到set-role rpc(sal-role.yang)向Switch发出本地是其Master/Slave节点。又如,在sal-echo.yang
中定义了OFP与Switch之间发现echo消息动作。而在OFP中对这些RPC都有实现,并通过此方式对北向应用提供服务。
对底层Switch下发流表的RPC在sal-flow.yang
中定义,其中定义了add-flow、remove-flow、update-flow三个RPC。而sal-flow.yang
的RPC实现在SalFlowServiceImpl中(org.opendaylight.openflowplugin.impl.services.sal.
)。下面我们具体展开下发流表这一个服务SalFlowService,希望读者可以举一反三,在需要对Switch进行其他操作时参考本文。
SalFlowService流服务
什么是SalFlowService
在sal-flow.yang
中定义的是对底层switch修改流表的rpc,以add-flow
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
rpc add-flow { description "Adding flow to openflow device."; input { uses tr:transaction-metadata; leaf flow-ref { type types:flow-ref; } uses node-flow; } output { uses tr:transaction-aware; } } ... |
在ODL底层已经实现了上面的RPC接口,在SalFlowServiceImpl
对象中实现了sal-flow.yang
中定义的RPC。SalFlowServiceImpl实现了sal-flow.yang
中的RPC,比如add-flow
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public Future<RpcResult<AddFlowOutput>> addFlow(final AddFlowInput input) { final FlowRegistryKey flowRegistryKey = FlowRegistryKeyFactory.create(deviceContext.getDeviceInfo().getVersion(), input); final ListenableFuture<RpcResult<AddFlowOutput>> future; if (flowAddMessage.canUseSingleLayerSerialization()) { future = flowAddMessage.handleServiceCall(input); Futures.addCallback(future, new AddFlowCallback(input, flowRegistryKey), MoreExecutors.directExecutor()); } else { future = flowAdd.processFlowModInputBuilders(flowAdd.toFlowModInputs(input)); Futures.addCallback(future, new AddFlowCallback(input, flowRegistryKey), MoreExecutors.directExecutor()); } return future; } |
那么在OFP底层是如何将实例化SalFlowServiceImpl
并注册到RPC实现(RpcProviderRegistry)的呢?北向应用是如何调用SalFlowServiceImpl
呢?
注册SalFlowService
回顾前几篇笔记,当ContextChainImpl对象作为singleton service选举,在某个节点称为leader/master后,就会调用各个context的instantiateServiceInstance方法。其中包括RpcContextImpl.instantiateServiceInstance
方法,在RpcContext的实例化服务实例的过程中,会注册openflowplugin/model
中定义的RPC的实现,其中就包括SalFlowServiceImpl。
实例化服务过程中,会初始化SalFlowServiceImpl
并调用RpcContextImpl.registerRpcServiceImplementation
注册为Routed RPC,所有的RPC实现都是以同样的方式注册为Routed Rpc。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import org.opendaylight.openflowplugin.impl.services.sal.SalFlowServiceImpl; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService; public static void registerServices(@Nonnull final RpcContext rpcContext, @Nonnull final DeviceContext deviceContext, final ExtensionConverterProvider extensionConverterProvider, //ExtensionConverterManagerImpl, 是在openflowPluginProvider中new的 final ConvertorExecutor convertorExecutor) { ... final SalFlowServiceImpl salFlowService = new SalFlowServiceImpl(rpcContext, deviceContext, convertorExecutor); ... // 注册salFlowService rpcContext.registerRpcServiceImplementation(SalFlowService.class, salFlowService); ... } |
RpcContextImpl.registerRpcServiceImplementation
方法会将SalFlowServiceImpl注册为Routed Rpc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext; @Override public <S extends RpcService> void registerRpcServiceImplementation(final Class<S> serviceClass, final S serviceInstance) { // 注册为routed rpc. 包括SalFlowService if (!rpcRegistrations.containsKey(serviceClass)) { final RoutedRpcRegistration<S> routedRpcReg = rpcProviderRegistry.addRoutedRpcImplementation(serviceClass, serviceInstance); // routed rpc的path为NodeContext.class nodeInstanceIdentifier // this.nodeInstanceIdentifier = deviceContext.getDeviceInfo().getNodeInstanceIdentifier(); routedRpcReg.registerPath(NodeContext.class, nodeInstanceIdentifier); rpcRegistrations.put(serviceClass, routedRpcReg); if (LOG.isDebugEnabled()) { LOG.debug("Registration of service {} for device {}.", serviceClass.getSimpleName(), nodeInstanceIdentifier.getKey().getId().getValue()); } } } |
那么什么是Routed Rpc,为什么将SalFlowServiceImpl注册为Routed Rpc,我们下面展开!
怎么使用SalFlowServiceImpl
在解释什么是Routed Rpc,为什么将SalFlowServiceImpl注册为Routed Rpc之前,我们先来认识怎么使用SalFlowServiceImpl。然后再看SalFlowServiceImpl是怎么和Router Rpc发生关系。
在北向应用中只需要调用SalFlowServiceImpl
提供的方法即可完成添加流、删除流、更新流的操作,因为封装了这些RPC。
北向应用使用SalFlowServiceImpl步骤如下:
(1)在blueprint中引用SalFlowService
1 2 |
<odl:rpc-service id="salFlowService" interface="org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService" /> |
注解:
odl:rpc-service
什么意思?是odl对blueprint的扩展,实现直接从OSGI在获取此RPC的implementation。参考
(2)在blueprint中实例化对象,可直接传入上述引用的salFlowService rpc-service,并在类中使用。
比如blueprint中实例化对象时,传入参数为salFlowService。
1 |
<argument ref="salFlowService" /> |
(3)最终在代码中调用,首先build AddFlowInput对象(RPC定义),然后直接调用salFlowService.addFlow方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput; public Future<RpcResult<AddFlowOutput>> addFlow(final InstanceIdentifier<Node> nodeId, final Flow flow) { final InstanceIdentifier<Table> tableInstanceId = getTableInstanceId(nodeId, flow.getTableId()); final InstanceIdentifier<Flow> flowInstanceId = getFlowInstanceId(tableInstanceId, flowIdInc.getAndIncrement()); final AddFlowInputBuilder addFlowInputBuilder = new AddFlowInputBuilder(flow) .setNode(new NodeRef(nodeId)) // 注意这是下发流表的NODE .setFlowRef(new FlowRef(flowInstanceId)) .setFlowTable(new FlowTableRef(tableInstanceId)) .setTransactionUri(new Uri(flow.getId().getValue())); // 直接调用salFlowService.addFlow return salFlowService.addFlow(addFlowInputBuilder.build()); } |
Routed Rpc
什么是Routed Rpc
不是所有RPC都能被注册为Routed Rpc,需要在YANG中有额外定义。
我们回到RPC的定义,以此add-flow为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
rpc add-flow { description "Adding flow to openflow device."; input { uses tr:transaction-metadata; leaf flow-ref { type types:flow-ref; } uses node-flow; } output { uses tr:transaction-aware; } } |
其中uses node-flow
语句中的的node-flow
grouping定义如下:
1 2 3 4 5 6 7 8 9 |
grouping node-flow { description "Top openflow flow structure suitable for rpc input (contains node-context)."; uses "inv:node-context-ref"; // inv是: import opendaylight-inventory {prefix inv;revision-date "2013-08-19";} leaf flow-table { type flow-table-ref; } uses types:flow; } |
node-flow
定义中的uses "inv:node-context-ref"
语句,在opendaylight-inventory.yang
中定义了node-context-ref如下,包含leaf节点node
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
grouping node-context-ref { status deprecated; description "A helper grouping which contains a reference to a node classified with a node-context. This allows RPCs in other yang files to refine their input to a particular node instance."; leaf node { ext:context-reference "node-context"; // 个人理解:用于routed rpc定义路由规则 type node-ref; // instance-identifier description "A reference to a particular node."; } } // leaf node节点中的"node-context" identity node-context { status deprecated; description "A node-context is a classifier for node elements which allows an RPC to provide a service on behalf of a particular element in the data tree."; } |
即add-flow
Rpc的input有一个leaf为node
,且其定义为:
1 2 3 4 5 |
leaf node { ext:context-reference "node-context"; // 个人理解:用于routed rpc定义路由规则 type node-ref; // instance-identifier description "A reference to a particular node."; } |
回到注册Router Rpc的代码,我们关注registerPath
方法,注册Routed Rpc:
1 2 3 4 |
final RoutedRpcRegistration<S> routedRpcReg = rpcProviderRegistry.addRoutedRpcImplementation(serviceClass, serviceInstance); // NodeContext.class是 org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext; routedRpcReg.registerPath(NodeContext.class, nodeInstanceIdentifier); |
(1)可以看到调用registerPath方法,传入参数了NodeContext.class
。此正是上述YANG定义leaf node的属性ext:context-reference "node-context";
。NodeContext.class
正是opendaylight-inventory.yang
的identity node-context
通过yangtools编译出来的类。
(2)调用registerPath方法,传入的第二个参数是nodeInstanceIdentifier代表的是当前这个rpcContext的switch(device)的nodeInstanceIdentifier
Routed Rpc的注册方法是需要注册Rpc定义的leaf node(包含属性ext:context-reference)
可参考:
1.https://ask.opendaylight.org/question/99/how-does-request-routing-works/
2.官方项目coretutorials
这样注册的Routed Rpc有什么特殊之处?
这样做,在调用add-flow rpc时,ODL底层RPC系统会根据input中的node(leaf)的值进行路由!关于将SalFlowServiceImpl注册为Routed Rpc有什么用?为什么不是普通的Local Rpc?在下面揭晓!
集群情况下的SalFlowServiceImpl(Cluster)
总结一下,每个Switch在某个节点选举Master后,会创建SalFlowServiceImpl对象,并将其注册为Routed Rpc,SalFlowServiceImpl对象就实现了更新流的RPC(sal-flow.yang),相当于每个Switch的SalFlowServiceImpl对象封装了对Switch流的操作。
然而在集群的环境下,每个Switch只会有一个SalFlowServiceImpl
在其Master的控制节点上。那么能否在其他Slave节点上,对Switch进行修改流的操作?由于注册的是Routed Rpc,在集群任何节点上都可以对任意一个Switch操作流。比如在一个Switch的Slave控制节点上对此Switch添加流add-flow
,也能下发流表成功。
在下发流表的代码实现中,我们需要像上述使用方式一样build 带switch的nodeInstanceIdentifer的AddFlowInput对象,然后调用SalFlowService
接口,底层Rpc系统就根据nodeInstanceIdentifer值,找到nodeInstanceIdentifer关联的Rpc实现SalFlowServiceImpl
(注意与SalFlowService的不同)。ODL底层Rpc系统会将此请求(add-flow)路由到Switch的Master控制节点的SalFlowServiceImpl
中,然后对Switch下发流表(在Master节点下发流表)。
总结:
- 1.每个Switch(Device)都会在它的Master的控制节点上,实例化
SalFlowServiceImpl
对象并注册到RPC系统,并注册为Routed Rpc,且Rpc的标识是Switch的nodeInstanceIdentifier
; - 2.所以上层应用在调用
SalFlowService
接口的addFlow
方法,底层RPC系统会根据传入的AddFlowInput input
中的leaf node
的值(nodeInstanceIdentifier)能够决定最终调用到的是哪个具体的SalFlowServiceImpl
实现。
这就是routed rpc,注册时需要注册path(包括类型和值),调用时根据值路由到对应的rpc实现。也是为什么OFP是把所有RPC实现都注册为Router Rpc。
总结
本文,我们以SalFlowServiceImpl为例子,展开描述OFP在哪定义Switch操作的Rpc、应用中如何调用Rpc;并且我们深入了每个Switch是如何注册Routed Rpc、什么是Routed Rpc、Router Rpc有什么作用。
希望读者不仅知道SalFlowService怎么使用,还能够理解OFP中关于Switch的Rpc的细节,能够根据以上对SalFlowService的分析,我们同样可以按一样的方式使用其他服务,比如:SalGroupServiceImpl
等等。