ODL netconf挂载点操作设备

来源:互联网 发布:centos 开启snmp 编辑:程序博客网 时间:2024/05/25 16:40

学习Netconf协议中,在与设备交互中,存在挂载点概念,操作该挂载点即操作设备。下面分析下这里的实现机制:

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


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

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

All simulated devices started successfully from port 17830 to 17830


启动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>

通过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中

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

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

方法最终来到NetconfClientDispatcherImpl中createSshClient,开始真正连接节点


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

    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以及去除挂载点的信息等。但设备重新上线时,是如何监听到并触发onSessinUp的呢?


进入NetconfDevice的onRemoteSessionUp,在这其中包装NetconfDeviceRpc,并进行Schema的build,成功后

调用setUpSchema,执行RecursiveSchemaSetup,进行其run方法中

之后 进入handleSalInitializationSuccess

  protected void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc) {        NetconfMessageTransformer.BaseSchema baseSchema =                remoteSessionCapabilities.isNotificationsSupported() ?                NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :                NetconfMessageTransformer.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的创建与设置




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


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


在接受请求时,查看入口

    @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进行远程节点(设备)报文发送。实现设备的直接控制。

原创粉丝点击