__version__ = "0.01"
-class NETCONFClientError(Exception): pass
\ No newline at end of file
+
+class ncclientError(Exception): pass
+
+class NETCONFError(ncclientError): pass
+
+# Decorators
+
+def override(func): func
--- /dev/null
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .. import ncclientError, NETCONFError
+
+class ContentError(ncclientError): pass
+
def has_listener(self, listener):
return (listener in self._listeners)
- def add_listner(self, listener):
+ def add_listener(self, listener):
self._listeners.append(listener)
def remove_listener(self, listener):
# See the License for the specific language governing permissions and
# limitations under the License.
-import threading
+from content import Creator, Parser
-class SessionError(NETCONFClientError): pass
+from threading import Thread
+from listener import Subject, Listener
-class Session(threading.Thread):
-
- def __init__(self, capabilities, reply_cb):
- Thread.__init__(self)
- self.capabilities = {
- 'client': capabilities,
- 'server': None #yet
- }
- self._q = Queue.Queue()
- self._cb = reply_cb
+class SessionError(ncclientError): pass
+
+class Session(Thread, Subject, Listener):
+
+ def __init__(self, capabilities=None):
+ Thread.__init__(self, name='session')
+ Subject.__init__(self, listeners=[self])
+ Thread.setDaemon(True)
+ self.client_capabilities = capabilities
+ self.server_capabilities = None # yet
self.id = None # session-id
- self.connected = False
+ self.is_connected = False
+ self._q = Queue.Queue()
def _make_hello(self):
- # <capabilities> should be repr(capabilities['client'])
- # rest, hmm..
pass
- def _init(self, msg):
- # session-id =
- # capabilities['server'] =
- self.connected = True
+ def _init(self, id, capabilities):
+ self.id = id
+ self.capabilities[SERVER] = capabilities
+ self.is_connected = True
+
+ @override
+ def _close(self):
+ raise NotImplementedError
def connect(self):
- self.start()
+ self._greet()
+ Thread.start()
+
+ def send(self, msg):
+ if self.is_connected:
+ self._q.add(msg)
+ else:
+ raise SessionError('Attempted to send message while not connected')
+ ### Thread methods
+
+ @override
def run(self):
raise NotImplementedError
-
- def send(self, msg):
- if self.connected:
- self._q.add(msg)
+
+ ### Subject methods
+
+ def add_listener(self, listener):
+ if not self.is_connected:
+ raise SessionError('Listeners may only be added after session initialisation')
else:
- raise SessionError('''Attempted to send message before
- NETCONF session initialisation''')
-
\ No newline at end of file
+ Subject.add_listner(self, listener)
+
+ ### Listener methods
+ # these are relevant for the initial greeting only
+
+ def reply(self, data):
+ p = Parser(data)
+ s = p['session']
+ id = s['@id']
+ capabilities = Capabilities()
+ capabilities.fromXML(p['capabilities'])
+ self._init(id, capabilities)
+ self.remove_listener(self)
+
+ def error(self, data):
+ self._close()
+ raise SSHError('Session initialization failed')
+
\ No newline at end of file
class SSHSession(Session):
BUF_SIZE = 4096
-
MSG_DELIM = ']]>>]]>'
+ MSG_DELIM_LEN = len(MSG_DELIM)
- def __init__(self, capabilities, reply_cb=None,
- load_known_hosts=True,
+ def __init__(self, capabilities, load_known_hosts=True,
missing_host_key_policy=paramiko.RejectPolicy):
Session.__init__(self, capabilities)
self._inBuf = ''
self._client = SSHClient()
if load_known_hosts:
self._client.load_system_host_keys()
- self._client.set_missing_host_key_policy(host_key_policy)
-
- def _greet_cb(self, reply):
- self._init(reply)
- self._cb = self._real_cb
-
- def _greet(self):
- self._real_cb = self._cb
- self._cb = self._greet_cb
- self._q.add(self._make_hello())
-
+ self._client.set_missing_host_key_policy(missing_host_key_policy)
+
def load_host_keys(self, filename):
self._client.load_host_keys(filename)
-
+
def set_missing_host_key_policy(self, policy):
self._client.set_missing_host_key_policy(policy)
-
- def set_reply_callback(self, cb):
- self._cb = cb
def connect(self, hostname, port=830, username=None, password=None,
key_filename=None, timeout=None, allow_agent=True,
self._channel = transport.open_session()
self._channel.invoke_subsystem('netconf')
self._greet()
- Session.connect(self) # starts thread
+ self.start()
+
+ def _close(self):
+ self._channel.shutdown(2)
def run(self):
sock = self._channel
data = sock.recv(BUF_SIZE)
if data:
self._inBuf += data
- # probably not very efficient:
- pos = self._inBuf.find(DELIM)
- if pos != -1:
- msg = self._inBuf[:pos]
- self._inBuf = self._inBuf[(pos + len(DELIM)):]
- self._reply_cb(msg)
+ (before, _, after) = self._inBuf.partition(MSG_DELIM)
+ if after:
+ # we don't want this thread to ground to a halt
+ # because of an error dispatching one reply...
+ try: self.dispatch('reply', before)
+ except: pass
+ self._inBuf = after
else:
- connected = False
- # it's not an error if we asked for it
- # via CloseSession -- need a way to know this
- raise SessionError
-
+ self.dispatch('error', self._inBuf)
+
+
class CallbackPolicy(paramiko.MissingHostKeyPolicy):
def __init__(self, cb):