git-svn-id: http://ncclient.googlecode.com/svn/trunk@18 6dbcf712-26ac-11de-a2f3-13738...
authorShikhar Bhushan <shikhar@schmizz.net>
Fri, 17 Apr 2009 03:54:26 +0000 (03:54 +0000)
committerShikhar Bhushan <shikhar@schmizz.net>
Fri, 17 Apr 2009 03:54:26 +0000 (03:54 +0000)
src/__init__.py
src/content/__init__.py [new file with mode: 0644]
src/listener.py
src/session.py
src/ssh.py

index 655152b..5984b46 100644 (file)
 
 __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
diff --git a/src/content/__init__.py b/src/content/__init__.py
new file mode 100644 (file)
index 0000000..912cfdb
--- /dev/null
@@ -0,0 +1,18 @@
+# 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
+
index aa16dfe..a32fdc5 100644 (file)
@@ -20,7 +20,7 @@ class Subject:
     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):
index 02edb00..b10c3f0 100644 (file)
 # 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
index 4076efd..bb75292 100644 (file)
@@ -26,11 +26,10 @@ class SSHError(SessionError): pass
 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 = ''
@@ -38,25 +37,13 @@ class SSHSession(Session):
         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,
@@ -69,7 +56,10 @@ class SSHSession(Session):
         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
@@ -87,18 +77,17 @@ class SSHSession(Session):
                 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):