ODL netconf挂载点操作设备

学习Netconf协议中,在与设备交互中,存在挂载点概念,操作该挂载点等同于操作该设备(实际是通过下发设备支持报文)。

下面分析下这里的实现机制:

为实现设备挂载,首先需要设备,使用ODL提供的设备模拟器,启动:

java -jar netconf-testtool-1.0.5-2.00.10R1B05-executable.jar

如果启动正常,则最后打印:

该模拟工具,还支持指定参数配置,包括--device--count --devices-per-port --starting-port等

启动ODL控制,安装指定feature

feature:install oscp-netconf-mdsal oscp-netconf-connector-all oscp-restconf-all oscp-netconf-topology

由于碳版本才有CallHome功能,所以这里需要通过控制器配置设备的相关信息,包括ip、port、userName、password等

http://X.X.X.X:8080/restconf/config/network-topology:network-topology/topology/topology-netconf/node/teste

<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
   <node-id>teste</node-id>
   <host xmlns="urn:opendaylight:netconf-node-topology">10.42.94.233</host>
   <port xmlns="urn:opendaylight:netconf-node-topology">17830</port>
   <username xmlns="urn:opendaylight:netconf-node-topology">admin</username>
   <password xmlns="urn:opendaylight:netconf-node-topology">admin</password>
   <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>
   <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">0</keepalive-delay>  
   <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">true</reconnect-on-changed-schema>
 </node>

说明:keepalive-delay是指保活心跳的时间间隔,默认为120s,配置0则表示无保活心跳机制。

sleep-factor使用的退避机制,用于计算每次设备断链重连的时间间隔

        leaf sleep-factor {
            config true;
            type decimal64 {
                fraction-digits 1;
            }
            default 1.5;
        }

        // Keepalive configuration
        leaf keepalive-delay {
            config true;
            type uint32;
            default 120;
            description "Netconf connector sends keepalive RPCs while the session is idle, this delay specifies the delay between keepalive RPC in seconds
                         If a value <1 is provided, no keepalives will be sent";
        }
通过PUT请求,往Config库topology-netconf中写入teste节点

一旦写入完成后,后序一系统动作则开始执行(通过数据库变更通知触发),包括连接设备及交互、创建挂载点、写Operational库等。

NetconfTopologyImpl监听了路径在topology-netconf中数据库变更通知,开始执行设备连接

 @Override
    public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Node>> collection) {
        for (DataTreeModification<Node> change : collection) {
            final DataObjectModification<Node> rootNode = change.getRootNode();
            LOG.info("SQ BUG LOCATION4: size:{},node modification type:{},id{}",collection.size(),rootNode.getModificationType(),getNodeId(rootNode.getIdentifier()));
            switch (rootNode.getModificationType()) {
                case SUBTREE_MODIFIED:
                    LOG.info("Config for node {} updated", getNodeId(rootNode.getIdentifier()));
                    disconnectNode(getNodeId(rootNode.getIdentifier()));
                    connectNode(getNodeId(rootNode.getIdentifier()), rootNode.getDataAfter());
                    break;
                case WRITE:
                    LOG.info("Config for node {} created", getNodeId(rootNode.getIdentifier()));
                    if (activeConnectors.containsKey(getNodeId(rootNode.getIdentifier()))) {
                        LOG.warn("RemoteDevice{{}} was already configured, reconfiguring..", getNodeId(rootNode.getIdentifier()));
                        disconnectNode(getNodeId(rootNode.getIdentifier()));
                    }
                    connectNode(getNodeId(rootNode.getIdentifier()), rootNode.getDataAfter());
                    break;
                case DELETE:
                    LOG.info("Config for node {} deleted", getNodeId(rootNode.getIdentifier()));
                    disconnectNode(getNodeId(rootNode.getIdentifier()));
                    break;
            }
        }
    }

调用AbstractNetconfTopology.java steupConnection开始连接设备,组织参数,创建NetconfDeviceCommunicator

final ListenableFuture<NetconfDeviceCapabilities> future = deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);

其中clientConfig配置,则包含上面写入Config库的节点信息

调用来到NetconfClientDispatcherImpl中的createClient,根据协议类型,分别选择TCP,SSH,TLS

如createSshClient,开始Nettty建链

一量连接成功,监听连接建立的监听器,则开始触发

    private Future<NetconfClientSession> createSshClient(final NetconfClientConfiguration currentConfiguration) {
        LOG.debug("Creating SSH client with configuration: {}", currentConfiguration);
        return super.createClient(currentConfiguration.getAddress(), currentConfiguration.getReconnectStrategy(),
                new PipelineInitializer<NetconfClientSession>() {

                    @Override
                    public void initializeChannel(final SocketChannel ch,
                                                  final Promise<NetconfClientSession> sessionPromise) {
                        new SshClientChannelInitializer(currentConfiguration.getAuthHandler(),
                                getNegotiatorFactory(currentConfiguration), currentConfiguration.getSessionListener())
                                .initialize(ch, sessionPromise);
                    }

                });
    }

这里的监听器存在于currentConfiguration中,创建于AbstractNetconfTopology的steupConnection,getClientConfig,

且由已经准备好了createDeviceCommunicator传入listener

        NetconfDeviceCommunicator communicator = userCapabilities.isPresent() ?
                new NetconfDeviceCommunicator(
                        remoteDeviceId, device, new UserPreferences(userCapabilities.get(), node.getYangModuleCapabilities().isOverride())):
                new NetconfDeviceCommunicator(remoteDeviceId, device);


NetconfDeviceCommunicator继承了SessionListener,其中onSessionUp在连接成功后回调触发 ,同理onSessionDown则在设备下线时,进行设备下线逻辑的处理,包括置当前设备的状态为connecting以及消除设备capabilities以及去除挂载点的信息等。


进入NetconfDevice的onRemoteSessionUp,该方法中,执行DeviceSourcesResolver任务,该任务的核心是执行GetSchemaRPC

public static final QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get");

成功,则调用setUpSchema,执行RecursiveSchemaSetup,该类作用:Schema builder that tries to build schema context from provided sources or biggest subset of it.

若schemaContext构建成功,则执行handleSalInitializationSuccess,并传入DeviceRPC

getDeviceSpecificRpc(result, baseSchema)

    protected void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc) {
        BaseSchema baseSchema =
                remoteSessionCapabilities.isNotificationsSupported() ?
                BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
                BaseSchema.BASE_NETCONF_CTX;
        messageTransformer = new NetconfMessageTransformer(result, true, baseSchema);

        updateTransformer(messageTransformer);
        // salFacade.onDeviceConnected has to be called before the notification handler is initialized
        salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc);
        notificationHandler.onRemoteSchemaUp(messageTransformer);

        LOG.info("{}: Netconf connector initialized successfully", id);
    }

salFacade.onDeviceConnected,并在之后创建的mountpoint中保存

NetconfDeviceSalFacade onDeviceConnected

在该方法内部,则进行了moutpoint设置

MoutPoint的创建在NetconfDeviceSalProvider中onSessionInitiated方法中

    @Override
    public void onSessionInitiated(final Broker.ProviderSession session) {
        logger.debug("{}: (BI)Session with sal established {}", id, session);

        final DOMMountPointService mountService = session.getService(DOMMountPointService.class);
        if (mountService != null) {
            mountInstance = new MountInstance(mountService, id, node);
        }
    }


在Rest请求到达时,再根据id取对应的mountponit,然后就能找到那个session进行发送数据了

至此所谓的mountpoint是真实设备的挂载点,实现逻辑就正确 了。如下:


在接受Rest请求时,查看入口

    @Override
    public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
        final InstanceIdentifierContext<?> iiWithData = controllerContext.toInstanceIdentifier(identifier);
        final DOMMountPoint mountPoint = iiWithData.getMountPoint();
        NormalizedNode<?, ?> data = null;
        final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
        if (mountPoint != null) {
            data = broker.readConfigurationData(mountPoint, normalizedII);
        } else {
            data = broker.readConfigurationData(normalizedII);
        }
        if(data == null) {
            final String errMsg = "Request could not be completed because the relevant data model content does not exist ";
            LOG.debug(errMsg + identifier);
            throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.DATA_MISSING);
        }
        return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo));
    }

作为对比,无MountPoint的调用为:

public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
        checkPreconditions();
        return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
    }
使用的是domDataBroker,而这个broker是配置子系统加载的

根据是否能查到mountPoint处理逻辑是不一样的

    public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(@Nonnull final SchemaPath type, @Nullable final NormalizedNode<?, ?> input) {
        final NetconfMessage message = transformer.toRpcRequest(type, input);
        final ListenableFuture<RpcResult<NetconfMessage>> delegateFutureWithPureResult = listener.sendRequest(message, type.getLastComponent());

        final ListenableFuture<DOMRpcResult> transformed = Futures.transform(delegateFutureWithPureResult, new Function<RpcResult<NetconfMessage>, DOMRpcResult>() {
            @Override
            public DOMRpcResult apply(final RpcResult<NetconfMessage> input) {
                if (input.isSuccessful()) {
                    return transformer.toRpcResult(input.getResult(), type);
                } else {
                    // TODO check whether the listener sets errors properly
                    return new DefaultDOMRpcResult(input.getErrors());
                }
            }
        });

        return Futures.makeChecked(transformed, new Function<Exception, DOMRpcException>() {
            @Nullable
            @Override
            public DOMRpcException apply(@Nullable final Exception e) {
                // FIXME what other possible exceptions are there ?
                return new DOMRpcImplementationNotAvailableException(e, "Unable to invoke rpc %s", type);
            }
        });
    }
最终的RPC调用,会使用其listener进行远程节点(设备)报文发送。实现设备的直接控制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值