# limitations under the License.
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
+CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
def qualify(tag, namespace=None):
'Returns qualified name of form `{namespace}tag`'
- if namespace is None:
- return tag
- else:
+ if namespace is not None:
return '{%s}%s' % (namespace, tag)
+ else:
+ return tag
+
+unqualify = lambda tag: tag[tag.rfind('}')+1:]
# limitations under the License.
from xml.etree import cElementTree as ET
+from cStringIO import StringIO
from common import BASE_NS
from common import qualify as _
+from common import unqualify as __
class HelloParser:
'Returns tuple of (session-id, ["capability_uri", ...])'
sid, capabilities = 0, []
root = ET.fromstring(raw)
- # cisco spews un-namespaced xml
- htag = ('hello', _('hello', BASE_NS))
- stag = ('session-id', _('session-id', BASE_NS))
- ctag = ('capabilities', _('capabilities', BASE_NS))
- if root.tag in htag:
+ # cisco's too posh to namespace its hello
+ if __(root.tag) == 'hello':
for child in root.getchildren():
- if child.tag in stag:
+ if __(child.tag) == 'session-id':
sid = child.text
- elif child.tag in ctag:
+ elif __(child.tag) == 'capabilities':
for cap in child.getiterator('capability'):
capabilities.append(cap.text)
for cap in child.getiterator(_('capability', BASE_NS)):
capabilities.append(cap.text)
return sid, capabilities
-
class RootParser:
'''Parser for the top-level element of an XML document. Does not look at any
sub-elements. It is useful for efficiently determining the type of received
messages.
'''
- def __init__(self, recognize=[]):
- self._recognized = recognize
-
- def recognize(self, element):
- '''Specify an element that should be successfully parsed.
-
- element should be a string that represents a qualified name of the form
- `{namespace}tag`.
- '''
- self._recognized.append(element)
-
- def parse(self, raw):
+ @staticmethod
+ def parse(raw, recognized=[]):
'''Parse the top-level element from a string representing an XML document.
+ recognized is a list of tag names that will be successfully parsed.
+ The tag names should not be qualified. This is for simplicity of parsing
+ where the NETCONF implementation is non-compliant (e.g. cisco's which
+ uses an incorrect namespace)
+
Returns a `(tag, attributes)` tuple, where `tag` is a string representing
the qualified name of the recognized element and `attributes` is an
`{attribute: value}` dictionary.
'''
fp = StringIO(raw)
for event, element in ET.iterparse(fp, events=('start',)):
- for ele in self._recognized:
- if element.tag == ele:
- return (element.tag, element.attrib)
+ for ele in recognized:
+ if __(element.tag) == ele:
+ attrs = {}
+ for attr in element.attrib:
+ attrs[__(attr)] = element.attrib[attr]
+ return (ele, attrs)
break
- return None
\ No newline at end of file
+
+
+class RPCReplyParser:
+
+ @staticmethod
+ def parse(raw):
+ pass
#from notification import CreateSubscription
__all__ = [
-# 'Get',
-# 'GetConfig',
-# 'EditConfig',
-# 'DeleteConfig',
-# 'Lock',
-# 'Unlock',
+ #'Get',
+ #'GetConfig',
+ #'EditConfig',
+ #'DeleteConfig',
+ 'Lock',
+ 'Unlock',
'CloseSession',
'KillSession',
# 'CreateSubscription',
-# ]
\ No newline at end of file
+]
class Lock(RPC):
- def __init__(self):
- RPC.__init__(self)
+ def __init__(self, session):
+ RPC.__init__(self, session)
self.spec = {
'tag': 'lock',
- 'children': [ { 'tag': 'target', 'text': None } ]
+ 'children': { 'tag': 'target', 'children': {'tag': None} }
}
def request(self, target='running', reply_event=None):
- self.spec['children'][0]['text'] = target
+ self.spec['children']['children']['tag'] = target
self._do_request(self.spec, reply_event)
-class Unock(RPC):
+
+class Unlock(RPC):
- def __init__(self):
- RPC.__init__(self)
+ def __init__(self, session):
+ RPC.__init__(self, session)
self.spec = {
'tag': 'unlock',
- 'children': [ { 'tag': 'target', 'text': None } ]
+ 'children': { 'tag': 'target', 'children': {'tag': None} }
}
def request(self, target='running', reply_event=None):
- self.spec['children'][0]['text'] = target
+ self.spec['children']['children']['tag'] = target
self._do_request(self.spec, reply_event)
from threading import Event, Lock
from uuid import uuid1
-from weakref import WeakKeyDictionary, WeakValueDictionary
+from weakref import WeakKeyDictionary
-from listener import get_listener
+from listener import SessionListener
from ncclient.content.builders import RPCBuilder
-from ncclient.content.parsers import RootParser
-from ncclient.content.common import qualify as _
-from ncclient.content.common import BASE_NS
class RPC:
@property
def _listener(self):
with self._lock:
- return self._listeners.setdefault(self._session, MessageListener())
+ return self._listeners.setdefault(self._session, SessionListener())
- def _response_cb(self, raw):
+ def deliver(self, raw):
self._reply = RPCReply(raw)
- reply_event.set()
+ self._reply_event.set()
def _do_request(self, op, reply_event=None):
self._id = uuid1().urn
# get the listener instance for this session
# <rpc-reply> with message id will reach response_cb
- self._listener.register(self._id, self._response_cb)
+ self._listener.register(self._id, self)
# only effective the first time, transport.session.Subject internally
# uses a set type for listeners
self._session.add_listener(self._listener)
def __str__(self):
return self._raw
-
- @property
- def raw(self):
- return self._raw
def parse(self):
#errs = RPCParser.parse(self._raw)
self._parsed = True
@property
+ def raw(self):
+ return self._raw
+
+ @property
def parsed(self):
return self._parsed
@property
def ok(self):
- return True if self._parsed and not self._err else False
+ return True if self._parsed and not self._errs else False
@property
def errors(self):
@property
def info(self):
return self._dict.get('info', None)
-
-
-class SessionListener:
-
- '''This is the glue between received data and the object it should be
- forwarded to.
- '''
-
- def __init__(self):
- # this dictionary takes care of <rpc-reply> elements received
- # { 'message-id': callback } dict
- self._id2cb = WeakValueDictionary()
- # this is a more generic dict takes care of other top-level elements
- # that may be received, e.g. <notification>'s
- # {'tag': callback} dict
- self._tag2cb = WeakValueDictionary()
- # if we receive a SessionCloseError it might not be one we want to act on
- self._expecting_close = False
- self._errback = None # error event callback
- self._lock = Lock()
-
- def __str__(self):
- return 'SessionListener'
-
- def register(self, msgid, cb):
- with self._lock:
- self._id2cb[msgid] = cb
-
- def recognize(self, tag, cb):
- with self._lock:
- self._tag2cb[tag] = cb
-
- def expect_close(self):
- self._expecting_close = True
-
- @property
- def _recognized_elements(self):
- elems = [_('rpc-reply', BASE_NS)]
- with self._lock:
- elems.extend(self._tag2cb.keys())
- return elems
-
- def reply(self, raw):
- tag, attrs = RootParser.parse(raw, self._recognized_elements)
- try:
- cb = None
- if tag == _('rpc-reply', BASE_NS):
- try:
- id = attrs[_('message-id', BASE_NS)]
- except KeyError:
- logger.warning('<rpc-reply> w/o message-id attr received: %s'
- % raw)
- cb = self._id2cb.get(id, None)
- else:
- cb = self._tag2cb.get(tag, None)
- if cb is not None:
- cb(raw)
- except Exception as e:
- logger.warning('SessionListener.reply: %r' % e)
-
- def set_errback(self, errback):
- self._errback = errback
-
- def error(self, err):
- from ncclient.transport.error import SessionCloseError
- act = True
- if isinstance(err, SessionCloseError):
- logger.debug('session closed, expecting_close=%s' %
- self._expecting_close)
- if self._expecting_close:
- act = False
- if act:
- logger.error('SessionListener.error: %r' % err)
- if self._errback is not None:
- errback(err)
'Session-related NETCONF operations'
-from ncclient.content.parsers import RPCParser
from rpc import RPC
class CloseSession(RPC):
- def __init__(self):
- RPC.__init__(self)
+ def __init__(self, session):
+ RPC.__init__(self, session)
self.spec = { 'tag': 'close-session' }
- def _response_cb(self, reply):
- RPC._response_cb(self, reply)
- if RPCParser.parse_ok(reply):
+ def deliver(self, reply):
+ RPC.deliver(self, reply)
+ self._reply.parse()
+ if self._reply.ok:
self._listener.expect_close()
self._session.close()
- def request(self, *args, **kwds):
- self._do_request(spec, *args, **kwds)
+ def request(self, reply_event=None):
+ self._do_request(self.spec, reply_event)
class KillSession(RPC):
- def __init__(self):
- RPC.__init__(self)
+ def __init__(self, session):
+ RPC.__init__(self, session)
self.spec = {
'tag': 'kill-session',
'children': [ { 'tag': 'session-id', 'text': None} ]
def __init__(self, in_buf, out_buf=None):
msg = 'Unexpected session close.'
if in_buf:
- msg += '.. IN_BUFFER: |%s| ' % in_buf
+ msg += ' .. IN_BUFFER: ||%s|| ' % in_buf
if out_buf:
- msg += '.. OUT_BUFFER: |%s|' % out_buf
+ msg += ' .. OUT_BUFFER: ||%s||' % out_buf
SSHError.__init__(self, msg)
class SSHError(TransportError):
with self._lock:
listeners = list(self._listeners)
for l in listeners:
+ logger.debug('dispatching [%s] to [%s]' % (event, l))
try:
- logger.debug('dispatching [%s] to [%s]' % (event, l))
getattr(l, event)(*args, **kwds)
+ except AttributeError as e:
+ logger.debug('Subject.dispatch: %r' % e)
except Exception as e:
- pass # if a listener doesn't care for some event we don't care
+ logger.warning('Subject.dispatch: %r' % e)
class Session(Thread, Subject):
logger.debug('queueing:%s' % message)
self._q.put(message)
- def connect(self):
+ def connect(self, *args, **kwds):
raise NotImplementedError
def run(self):
else: # if we didn't break out of the loop, full delim was parsed
msg_till = buf.tell() - n
buf.seek(0)
- msg = buf.read(msg_till)
- self.dispatch('received', msg)
+ self.dispatch('received', buf.read(msg_till).strip())
buf.seek(n+1, os.SEEK_CUR)
rest = buf.read()
buf = StringIO()
return 'DebugListener'
def received(self, raw):
- logger.debug('DebugListener:[received]:%s' % raw)
+ logger.debug('DebugListener:[received]:||%s||' % raw)
def error(self, err):
logger.debug('DebugListener:[error]:%r' % err)