lib/net/netconf/transport.rb
## -----------------------------------------------------------------------
## This file contains the Netconf::Transport parent class definition.
## All other transports, i.e. "ssh", "serial", "telnet" use this parent
## class to define their transport specific methods:
##
## trans_open: open the transport connection
## trans_close: close the transport connection
## trans_send: send XML command (String) via transport
## trans_receive: receive XML response (String) via transport
##
## -----------------------------------------------------------------------
module Netconf
class Transport
attr_reader :rpc, :state, :session_id, :capabilities
attr_writer :timeout, :waitio
def initialize( &block )
@state = :NETCONF_CLOSED
@os_type = @args[:os_type] || Netconf::DEFAULT_OS_TYPE
@rpc = Netconf::RPC::Executor.new( self, @os_type )
@rpc_message_id = 1
if block_given?
open( &block = nil ) # do not pass this block to open()
yield self
close()
end
end # initialize
def open?
@state == :NETCONF_OPEN
end
def closed?
@state == :NECONF_CLOSED
end
def open( &block ) # :yield: specialized transport open, generally not used
raise Netconf::StateError if @state == :NETCONF_OPEN
# block is used to deal with special open processing ...
# this is *NOT* the block passed to initialize()
raise Netconf::OpenError unless trans_open( &block )
# read the <hello> from the server and parse out
# the capabilities and session-id
hello_rsp = Nokogiri::XML( trans_receive_hello() )
hello_rsp.remove_namespaces!
@capabilities = hello_rsp.xpath('//capability').map{ |c| c.text }
@session_id = hello_rsp.xpath('//session-id').text
# send the <hello>
trans_send_hello()
@state = :NETCONF_OPEN
self
end
def trans_receive_hello
trans_receive()
end
def trans_send_hello
trans_send( Netconf::RPC::MSG_HELLO )
trans_send( RPC::MSG_END )
end
def has_capability?( capability )
@capabilities.select{|c| c.include? capability }.pop
# note: the caller could also simply use #grep on @capabilities
end
def close
raise Netconf::StateError unless @state == :NETCONF_OPEN
trans_close()
@state = :NETCONF_CLOSED
self
end
# string in; string out
def send_and_receive( cmd_str )
trans_send( cmd_str )
trans_send( RPC::MSG_END )
trans_receive()
end
def rpc_exec( cmd_nx )
raise Netconf::StateError unless @state == :NETCONF_OPEN
# add the mandatory message-id and namespace to the RPC
rpc_nx = cmd_nx.parent.root
rpc_nx.default_namespace = Netconf::NAMESPACE
rpc_nx['message-id'] = @rpc_message_id.to_s
@rpc_message_id += 1
# send the XML command through the transport and
# receive the response; then covert it to a Nokogiri XML
# object so we can process it.
rsp_nx = Nokogiri::XML( send_and_receive( cmd_nx.to_xml ))
# the following removes only the default namespace (xmlns)
# definitions from the document. This is an alternative
# to using #remove_namespaces! which would remove everything
# including vendor specific namespaces. So this approach is a
# nice "compromise" ... just don't know what it does
# performance-wise on large datasets.
rsp_nx.traverse{ |n| n.namespace = nil }
# set the response context to the root node; <rpc-reply>
rsp_nx = rsp_nx.root
# check for rpc-error elements. these could be
# located anywhere in the structured response
rpc_errs = rsp_nx.xpath('//self::rpc-error')
if rpc_errs.count > 0
# look for rpc-errors that have a severity == 'error'
# in some cases the rpc-error is generated with
# severity == 'warning'
sev_err = rpc_errs.xpath('error-severity[. = "error"]')
# if there are rpc-error with severity == 'error'
# or if the caller wants to raise if severity == 'warning'
# then generate the exception
if(( sev_err.count > 0 ) || Netconf::raise_on_warning )
exception = Netconf::RPC.get_exception( cmd_nx )
raise exception.new( self, cmd_nx, rsp_nx )
end
end
# return the XML with context at toplevel element; i.e.
# after the <rpc-reply> element
# @@@/JLS: might this be <ok> ? isn't for Junos, but need to check
# @@@/JLS: the generic case.
rsp_nx.element_children
end
end #--class: Transport
end #--module: Netconf