Revision c3d6fa74

/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from xml.etree import cElementTree as ElementTree
17
from cStringIO import StringIO
18

  
19
logger = logging.getLogger('ncclient.content')
20

  
21

  
22
def qualify(tag, ns=None):
23
    if ns is None:
24
        return tag
25
    else:
26
        return '{%s}%s' % (ns, tag)
27
_ = qualify
28

  
29
################################################################################
30

  
31
class Hello:
32
    
33
    NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
34
    
35
    @staticmethod
36
    def build(capabilities, encoding='utf-8'):
37
        hello = ElementTree.Element(_('hello', Hello.NS))
38
        caps = ElementTree.Element('capabilities')
39
        for uri in capabilities:
40
            cap = ElementTree.Element('capability')
41
            cap.text = uri
42
            caps.append(cap)
43
        hello.append(caps)
44
        tree = ElementTree.ElementTree(hello)
45
        fp = StringIO()
46
        tree.write(fp, encoding)
47
        return fp.getvalue()
48
    
49
    @staticmethod
50
    def parse(raw):
51
        'Returns tuple of (session-id, ["capability_uri", ...])'
52
        id, capabilities = 0, []
53
        root = ElementTree.fromstring(raw)
54
        if root.tag == _('hello', Hello.NS):
55
            for child in root.getchildren():
56
                if child.tag == _('session-id', Hello.NS):
57
                    id = int(child.text)
58
                elif child.tag == _('capabilities', Hello.NS):
59
                    for cap in child.getiterator(_('capability', Hello.NS)):
60
                        capabilities.append(cap.text)
61
        return id, capabilities
62

  
63
################################################################################
64

  
65
class RootElementParser:
66
    
67
    '''Parse the root element of an XML document. The tag and namespace of
68
    recognized elements, and attributes of interest can be customized.
69
    
70
    RootElementParser does not parse any sub-elements.
71
    '''
72
    
73
    def __init__(self, recognize=[]):
74
        self._recognize = recognize
75
    
76
    def recognize(self, element):
77
        '''Specify an element that should be successfully parsed.
78
        
79
        element should be a string that represents a qualified name of the form
80
        *{namespace}tag*.
81
        '''
82
        self._recognize.append((element, attrs))
83
    
84
    def parse(self, raw):
85
        '''Parse the root element from a string representing an XML document.
86
        
87
        Returns a (tag, attributes) tuple. tag is a string representing
88
        the qualified name of the recognized element. attributes is a
89
        {'attr': value} dictionary.
90
        '''
91
        fp = StringIO(raw)
92
        for event, element in ElementTree.iterparse(fp, events=('start',)):
93
            for e in self._recognize:
94
                if element.tag == e:
95
                    return (element.tag, element.attrib)
96
            break
97
        return None
98

  
99

  
100
################################################################################
101

  
102
class XMLBuilder:
103
    
104
    @staticmethod
105
    def _element(spec):
106
        element = ElementTree.Element(spec['tag'], spec.get('attrib', {}))
107
        for child in spec.get('children', []):
108
            element.append(XMLBuilder._element(child))
109
        return element
110
    
111
    @staticmethod
112
    def _etree(spec):
113
        return ElementTree.ElementTree(XMLBuilder._element(spec))
114
    
115
    @staticmethod
116
    def build(spec, encoding='utf-8'):
117
        fp = StringIO()
118
        XMLBuilder._etree(spec).write(fp, encoding)
119
        return fp.get_value()
b/ncclient/content/__init__.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
'This module serves as an XML abstraction layer'
16

  
17
class ContentError(Exception):
18
    pass
19

  
20
def qualify(tag, namespace=None):
21
    'Returns qualified name of form `{namespace}tag`'
22
    if namespace is None:
23
        return tag
24
    else:
25
        return '{%s}%s' % (namespace, tag)
26
_ = qualify
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from weakref import WeakValueDictionary
17

  
18
import content
19
from session import SessionCloseError
20

  
21
logger = logging.getLogger('ncclient.listeners')
22

  
23
################################################################################
24

  
25
# {session-id: SessionListener}
26
session_listeners = WeakValueDictionary
27
def session_listener_factory(session):
28
    try:
29
        return session_listeners[session]
30
    except KeyError:
31
        session_listeners[session] = SessionListener()
32
        return session_listeners[session]
33

  
34
class SessionListener:
35
    
36
    def __init__(self):
37
        # {message-id: RPC}
38
        self._rpc = WeakValueDictionary()
39
        # if the session gets closed by remote endpoint,
40
        # need to know if it is an error event or was requested through
41
        # a NETCONF operation i.e. CloseSession
42
        self._expecting_close = False
43
        # other recognized names 
44
        self._recognized = []
45
    
46
    def __str__(self):
47
        return 'SessionListener'
48
    
49
    def expect_close(self):
50
        self._expecting_close = True
51
    
52
    def register(self, id, op):
53
        self._id2rpc[id] = op
54
    
55
    ### Events
56
    
57
    def reply(self, raw):
58
        try:
59
            id = content.parse_message_root(raw)
60
            if id is None:
61
                pass
62
            elif id == 'notification':
63
                self._id2rpc[self._sub_id]._notify(raw)
64
            else:
65
                self._id2rpc[id]._response_cb(raw)
66
        except Exception as e:
67
            logger.warning(e)
68
    
69
    def error(self, err):
70
        if err is SessionCloseError:
71
            logger.debug('session closed by remote endpoint, expecting_close=%s' %
72
                         self._expecting_close)
73
            if not self._expecting_close:
74
                raise err
75

  
76
################################################################################
77

  
78
class HelloListener:
79
    
80
    def __str__(self):
81
        return 'HelloListener'
82
        
83
    def __init__(self, session):
84
        self._session = session
85
    
86
    ### Events
87
    
88
    def reply(self, data):
89
        try:
90
            id, capabilities = content.Hello.parse(data)
91
            logger.debug('HelloListener: session_id: %s; capabilities: %s', id, capabilities)
92
            self._session.initialize(id, capabilities)
93
        except Exception as e:
94
            self._session.initialize_error(e)
95
    
96
    def error(self, err):
97
        self._session.initialize_error(err)
98

  
99
################################################################################
100

  
101
class DebugListener:
102
    def __str__(self): return 'DebugListener'
103
    def reply(self, raw): logger.debug('DebugListener:reply:\n%s' % raw)
104
    def error(self, err): logger.debug('DebugListener:error:\n%s' % err)
b/ncclient/operations/rpc.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Event, Lock
16
from uuid import uuid1
17

  
18
import content
19
from listeners import session_listener_factory
20

  
21
class RPC:
22
    
23
    metadata = {
24
        'tag': 'rpc',
25
        'xmlns': 'urn:ietf:params:xml:ns:netconf:base:1.0',
26
        }
27
    
28
    def __init__(self, session, async=False, parse=True):
29
        self._session = session
30
        self._async = async
31
        self._id = uuid1().urn
32
        listener = session_listener_factory(self._session)
33
        listener.register(self._id, self)
34
        session.add_listener(listener)
35
        self._reply = None
36
        self._reply_event = Event()
37

  
38
    def _response_cb(self, reply):
39
        self._reply = reply
40
        self._event.set()
41
    
42
    def _do_request(self, operation):
43
        'operation is xml string'
44
        self._session.send(content.RPC.make(self._id, operation))
45
        if not self._async:
46
            self._reply_event.wait()
47
        return self._reply
48
    
49
    def request(self):
50
        raise NotImplementedError
51
    
52
    def wait_for_reply(self, timeout=None):
53
        self._reply_event.wait(timeout)
54
    
55
    @property
56
    def has_reply(self):
57
        return self._reply_event.isSet()
58
    
59
    @property
60
    def is_async(self):
61
        return self._async
62
    
63
    @property
64
    def reply(self):
65
        return self._reply
66
    
67
    @property
68
    def id(self):
69
        return self._id
70
    
71
    @property
72
    def session(self):
73
        return self._session
74

  
75
class RPCReply:
76
    
77
    class RPCError:
78
        
79
        pass
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Event, Lock
16
from uuid import uuid1
17

  
18
import content
19
from listeners import session_listener_factory
20

  
21
class RPC:
22
    
23
    metadata = {
24
        'tag': 'rpc',
25
        'xmlns': 'urn:ietf:params:xml:ns:netconf:base:1.0',
26
        }
27
    
28
    def __init__(self, session, async=False, parse=True):
29
        self._session = session
30
        self._async = async
31
        self._id = uuid1().urn
32
        listener = session_listener_factory(self._session)
33
        listener.register(self._id, self)
34
        session.add_listener(listener)
35
        self._reply = None
36
        self._reply_event = Event()
37

  
38
    def _response_cb(self, reply):
39
        self._reply = reply
40
        self._event.set()
41
    
42
    def _do_request(self, op):
43
        self._session.send(content.make_rpc(self._id, op))
44
        # content.make(RPC, attrs={'message-id': self._id}, children=(op,))
45
        if not self._async:
46
            self._reply_event.wait()
47
        return self._reply
48
    
49
    def request(self):
50
        raise NotImplementedError
51
    
52
    def wait_for_reply(self, timeout=None):
53
        self._reply_event.wait(timeout)
54
    
55
    @property
56
    def has_reply(self):
57
        return self._reply_event.isSet()
58
    
59
    @property
60
    def is_async(self):
61
        return self._async
62
    
63
    @property
64
    def reply(self):
65
        return self._reply
66
    
67
    @property
68
    def id(self):
69
        return self._id
70
    
71
    @property
72
    def session(self):
73
        return self._session
74

  
75
class RPCReply:
76
    
77
    class RPCError:
78
        
79
        pass
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from threading import Thread, Event
17
from Queue import Queue
18

  
19
import content
20
from capabilities import Capabilities, CAPABILITIES
21
from error import ClientError
22
from listeners import HelloListener
23
from subject import Subject
24

  
25
logger = logging.getLogger('ncclient.session')
26

  
27
class SessionError(ClientError): pass
28

  
29
class Session(Thread, Subject):
30
    
31
    def __init__(self):
32
        Thread.__init__(self, name='session')
33
        Subject.__init__(self)
34
        self._client_capabilities = CAPABILITIES
35
        self._server_capabilities = None # yet
36
        self._id = None # session-id
37
        self._error = None
38
        self._init_event = Event()
39
        self._q = Queue()
40
        self._connected = False # to be set/cleared by subclass implementation
41
    
42
    def _post_connect(self):
43
        # start the subclass' main loop
44
        listener = HelloListener(self)
45
        self.add_listener(listener)
46
        self.start()
47
        # queue client's hello message for sending
48
        self.send(content.Hello.build(self._client_capabilities))
49
        # we expect server's hello message, wait for _init_event to be set
50
        self._init_event.wait()
51
        self.remove_listener(listener)
52
        # there may have been an error
53
        if self._error:
54
            self._close()
55
            raise self._error
56
    
57
    def hello(self, id, capabilities):
58
        self._id, self._capabilities = id, Capabilities(capabilities)
59
        self._init_event.set()
60
    
61
    def hello_error(self, err):
62
        self._error = err
63
        self._init_event.set()
64
    
65
    def send(self, message):
66
        logger.debug('queueing message: \n%s' % message)
67
        self._q.put(message)
68
    
69
    def connect(self):
70
        raise NotImplementedError
71

  
72
    def run(self):
73
        raise NotImplementedError
74
        
75
    def capabilities(self, whose='client'):
76
        if whose == 'client':
77
            return self._client_capabilities
78
        elif whose == 'server':
79
            return self._server_capabilities
80
    
81
    ### Properties
82
    
83
    @property
84
    def client_capabilities(self):
85
        return self._client_capabilities
86
    
87
    @property
88
    def server_capabilities(self):
89
        return self._server_capabilities
90
    
91
    @property
92
    def connected(self):
93
        return self._connected
94
    
95
    @property
96
    def id(self):
97
        return self._id
b/ncclient/session/listeners.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from weakref import WeakValueDictionary
17

  
18
import content
19
from session import SessionCloseError
20

  
21
logger = logging.getLogger('ncclient.listeners')
22

  
23
################################################################################
24

  
25
# {session-id: SessionListener}
26
session_listeners = WeakValueDictionary
27
def session_listener_factory(session):
28
    try:
29
        return session_listeners[session]
30
    except KeyError:
31
        session_listeners[session] = SessionListener()
32
        return session_listeners[session]
33

  
34
class SessionListener:
35
    
36
    def __init__(self):
37
        # {message-id: RPC}
38
        self._rpc = WeakValueDictionary()
39
        # if the session gets closed by remote endpoint,
40
        # need to know if it is an error event or was requested through
41
        # a NETCONF operation i.e. CloseSession
42
        self._expecting_close = False
43
        # other recognized names 
44
        self._recognized = []
45
    
46
    def __str__(self):
47
        return 'SessionListener'
48
    
49
    def expect_close(self):
50
        self._expecting_close = True
51
    
52
    def register(self, id, op):
53
        self._id2rpc[id] = op
54
    
55
    ### Events
56
    
57
    def reply(self, raw):
58
        try:
59
            id = content.parse_message_root(raw)
60
            if id is None:
61
                pass
62
            elif id == 'notification':
63
                self._id2rpc[self._sub_id]._notify(raw)
64
            else:
65
                self._id2rpc[id]._response_cb(raw)
66
        except Exception as e:
67
            logger.warning(e)
68
    
69
    def error(self, err):
70
        if err is SessionCloseError:
71
            logger.debug('session closed by remote endpoint, expecting_close=%s' %
72
                         self._expecting_close)
73
            if not self._expecting_close:
74
                raise err
75

  
76
################################################################################
77

  
78
class HelloListener:
79
    
80
    def __str__(self):
81
        return 'HelloListener'
82
        
83
    def __init__(self, session):
84
        self._session = session
85
    
86
    ### Events
87
    
88
    def reply(self, data):
89
        try:
90
            id, capabilities = content.Hello.parse(data)
91
            logger.debug('HelloListener: session_id: %s; capabilities: %s', id, capabilities)
92
            self._session.initialize(id, capabilities)
93
        except Exception as e:
94
            self._session.initialize_error(e)
95
    
96
    def error(self, err):
97
        self._session.initialize_error(err)
98

  
99
################################################################################
100

  
101
class DebugListener:
102
    def __str__(self): return 'DebugListener'
103
    def reply(self, raw): logger.debug('DebugListener:reply:\n%s' % raw)
104
    def error(self, err): logger.debug('DebugListener:error:\n%s' % err)
b/ncclient/session/session.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from threading import Thread, Event
17
from Queue import Queue
18

  
19
import content
20
from capabilities import Capabilities, CAPABILITIES
21
from error import ClientError
22
from listeners import HelloListener
23
from subject import Subject
24

  
25
logger = logging.getLogger('ncclient.session')
26

  
27
class SessionError(ClientError): pass
28

  
29
class Session(Thread, Subject):
30
    
31
    def __init__(self):
32
        Thread.__init__(self, name='session')
33
        Subject.__init__(self)
34
        self._client_capabilities = CAPABILITIES
35
        self._server_capabilities = None # yet
36
        self._id = None # session-id
37
        self._error = None
38
        self._init_event = Event()
39
        self._q = Queue()
40
        self._connected = False # to be set/cleared by subclass implementation
41
    
42
    def _post_connect(self):
43
        # start the subclass' main loop
44
        listener = HelloListener(self)
45
        self.add_listener(listener)
46
        self.start()
47
        # queue client's hello message for sending
48
        self.send(content.Hello.build(self._client_capabilities))
49
        # we expect server's hello message, wait for _init_event to be set
50
        self._init_event.wait()
51
        self.remove_listener(listener)
52
        # there may have been an error
53
        if self._error:
54
            self._close()
55
            raise self._error
56
    
57
    def hello(self, id, capabilities):
58
        self._id, self._capabilities = id, Capabilities(capabilities)
59
        self._init_event.set()
60
    
61
    def hello_error(self, err):
62
        self._error = err
63
        self._init_event.set()
64
    
65
    def send(self, message):
66
        logger.debug('queueing message: \n%s' % message)
67
        self._q.put(message)
68
    
69
    def connect(self):
70
        raise NotImplementedError
71

  
72
    def run(self):
73
        raise NotImplementedError
74
        
75
    def capabilities(self, whose='client'):
76
        if whose == 'client':
77
            return self._client_capabilities
78
        elif whose == 'server':
79
            return self._server_capabilities
80
    
81
    ### Properties
82
    
83
    @property
84
    def client_capabilities(self):
85
        return self._client_capabilities
86
    
87
    @property
88
    def server_capabilities(self):
89
        return self._server_capabilities
90
    
91
    @property
92
    def connected(self):
93
        return self._connected
94
    
95
    @property
96
    def id(self):
97
        return self._id
b/ncclient/session/ssh.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from cStringIO import StringIO
17
from os import SEEK_CUR
18
import socket
19

  
20
import paramiko
21

  
22

  
23
from session import Session, SessionError
24

  
25
logger = logging.getLogger('ncclient.ssh')
26

  
27

  
28
class SessionCloseError(SessionError):
29
    
30
    def __str__(self):
31
        return 'RECEIVED: %s | UNSENT: %s' % (self._in_buf, self._out_buf)
32
    
33
    def __init__(self, in_buf, out_buf=None):
34
        SessionError.__init__(self)
35
        self._in_buf, self._out_buf = in_buf, out_buf
36

  
37

  
38
class SSHSession(Session):
39

  
40
    BUF_SIZE = 4096
41
    MSG_DELIM = ']]>]]>'
42
    
43
    def __init__(self, load_known_hosts=True,
44
                 missing_host_key_policy=paramiko.RejectPolicy()):
45
        Session.__init__(self)
46
        self._client = paramiko.SSHClient()
47
        self._channel = None
48
        if load_known_hosts:
49
            self._client.load_system_host_keys()
50
        self._client.set_missing_host_key_policy(missing_host_key_policy)
51
        self._in_buf = StringIO()
52
        self._parsing_state = 0
53
        self._parsing_pos = 0
54
    
55
    def _close(self):
56
        self._channel.close()
57
        self._connected = False
58
    
59
    def _fresh_data(self):
60
        delim = SSHSession.MSG_DELIM
61
        n = len(delim) - 1
62
        state = self._parsing_state
63
        buf = self._in_buf
64
        buf.seek(self._parsing_pos)
65
        while True:
66
            x = buf.read(1)
67
            if not x: # done reading
68
                break
69
            elif x == delim[state]:
70
                state += 1
71
            else:
72
                continue
73
            # loop till last delim char expected, break if other char encountered
74
            for i in range(state, n):
75
                x = buf.read(1)
76
                if not x: # done reading
77
                    break
78
                if x==delim[i]: # what we expected
79
                    state += 1 # expect the next delim char
80
                else:
81
                    state = 0 # reset
82
                    break
83
            else: # if we didn't break out of above loop, full delim parsed
84
                till = buf.tell() - n
85
                buf.seek(0)
86
                msg = buf.read(till)
87
                self.dispatch('reply', msg)
88
                buf.seek(n+1, SEEK_CUR)
89
                rest = buf.read()
90
                buf = StringIO()
91
                buf.write(rest)
92
                buf.seek(0)
93
                state = 0
94
        self._in_buf = buf
95
        self._parsing_state = state
96
        self._parsing_pos = self._in_buf.tell()
97

  
98
    #def load_host_keys(self, filename):
99
    #    self._client.load_host_keys(filename)
100
    #
101
    #def set_missing_host_key_policy(self, policy):
102
    #    self._client.set_missing_host_key_policy(policy)
103
    #
104
    #def connect(self, hostname, port=830, username=None, password=None,
105
    #            key_filename=None, timeout=None, allow_agent=True,
106
    #            look_for_keys=True):
107
    #    self._client.connect(hostname, port=port, username=username,
108
    #                        password=password, key_filename=key_filename,
109
    #                        timeout=timeout, allow_agent=allow_agent,
110
    #                        look_for_keys=look_for_keys)    
111
    #    transport = self._client.get_transport()
112
    #    self._channel = transport.open_session()
113
    #    self._channel.invoke_subsystem('netconf')
114
    #    self._channel.set_name('netconf')
115
    #    self._connected = True
116
    #    self._post_connect()
117

  
118
    def connect(self, hostname, port=830, username=None, password=None,
119
                key_filename=None, timeout=None, allow_agent=True,
120
                look_for_keys=True):
121
        self._transport = paramiko.Transport()
122
    
123
    def run(self):
124
        chan = self._channel
125
        chan.setblocking(0)
126
        q = self._q
127
        try:
128
            while True:    
129
                if chan.closed:
130
                    raise SessionCloseError(self._in_buf.getvalue())         
131
                if chan.send_ready() and not q.empty():
132
                    data = q.get() + SSHSession.MSG_DELIM
133
                    while data:
134
                        n = chan.send(data)
135
                        if n <= 0:
136
                            raise SessionCloseError(self._in_buf.getvalue(), data)
137
                        data = data[n:]
138
                if chan.recv_ready():
139
                    data = chan.recv(SSHSession.BUF_SIZE)
140
                    if data:
141
                        self._in_buf.write(data)
142
                        self._fresh_data()
143
                    else:
144
                        raise SessionCloseError(self._in_buf.getvalue())
145
        except Exception as e:
146
            logger.debug('*** broke out of main loop ***')
147
            self.dispatch('error', e)
148

  
149
class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
150
    
151
    def __init__(self, cb):
152
        self._cb = cb
153
    
154
    def missing_host_key(self, client, hostname, key):
155
        if not self._cb(hostname, key):
156
            raise SSHError
b/ncclient/session/subject.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Lock
16

  
17
import logging
18

  
19
logger = logging.getLogger('ncclient.subject')
20

  
21
class Subject:
22
        
23
    def __init__(self, listeners=[]):
24
        self._listeners = set(listeners)
25
        self._lock = Lock()
26
    
27
    def has_listener(self, listener):
28
        with self._lock:
29
            return (listener in self._listeners)
30
    
31
    def add_listener(self, listener):
32
        with self._lock:
33
            self._listeners.add(listener)
34
    
35
    def remove_listener(self, listener):
36
        with self._lock:
37
            self._listeners.discard(listener)
38
    
39
    def dispatch(self, event, *args, **kwds):
40
        # holding the lock while doing callbacks could lead to a deadlock
41
        # if one of the above methods is called
42
        with self._lock:
43
            listeners = list(self._listeners)
44
        for l in listeners:
45
            try:
46
                logger.debug('dispatching [%s] to [%s]' % (event, l))
47
                getattr(l, event)(*args, **kwds)
48
            except Exception as e:
49
                logger.warning(e)
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
import logging
16
from cStringIO import StringIO
17
from os import SEEK_CUR
18
import socket
19

  
20
import paramiko
21

  
22

  
23
from session import Session, SessionError
24

  
25
logger = logging.getLogger('ncclient.ssh')
26

  
27

  
28
class SessionCloseError(SessionError):
29
    
30
    def __str__(self):
31
        return 'RECEIVED: %s | UNSENT: %s' % (self._in_buf, self._out_buf)
32
    
33
    def __init__(self, in_buf, out_buf=None):
34
        SessionError.__init__(self)
35
        self._in_buf, self._out_buf = in_buf, out_buf
36

  
37

  
38
class SSHSession(Session):
39

  
40
    BUF_SIZE = 4096
41
    MSG_DELIM = ']]>]]>'
42
    
43
    def __init__(self, load_known_hosts=True,
44
                 missing_host_key_policy=paramiko.RejectPolicy()):
45
        Session.__init__(self)
46
        self._client = paramiko.SSHClient()
47
        self._channel = None
48
        if load_known_hosts:
49
            self._client.load_system_host_keys()
50
        self._client.set_missing_host_key_policy(missing_host_key_policy)
51
        self._in_buf = StringIO()
52
        self._parsing_state = 0
53
        self._parsing_pos = 0
54
    
55
    def _close(self):
56
        self._channel.close()
57
        self._connected = False
58
    
59
    def _fresh_data(self):
60
        delim = SSHSession.MSG_DELIM
61
        n = len(delim) - 1
62
        state = self._parsing_state
63
        buf = self._in_buf
64
        buf.seek(self._parsing_pos)
65
        while True:
66
            x = buf.read(1)
67
            if not x: # done reading
68
                break
69
            elif x == delim[state]:
70
                state += 1
71
            else:
72
                continue
73
            # loop till last delim char expected, break if other char encountered
74
            for i in range(state, n):
75
                x = buf.read(1)
76
                if not x: # done reading
77
                    break
78
                if x==delim[i]: # what we expected
79
                    state += 1 # expect the next delim char
80
                else:
81
                    state = 0 # reset
82
                    break
83
            else: # if we didn't break out of above loop, full delim parsed
84
                till = buf.tell() - n
85
                buf.seek(0)
86
                msg = buf.read(till)
87
                self.dispatch('reply', msg)
88
                buf.seek(n+1, SEEK_CUR)
89
                rest = buf.read()
90
                buf = StringIO()
91
                buf.write(rest)
92
                buf.seek(0)
93
                state = 0
94
        self._in_buf = buf
95
        self._parsing_state = state
96
        self._parsing_pos = self._in_buf.tell()
97

  
98
    #def load_host_keys(self, filename):
99
    #    self._client.load_host_keys(filename)
100
    #
101
    #def set_missing_host_key_policy(self, policy):
102
    #    self._client.set_missing_host_key_policy(policy)
103
    #
104
    #def connect(self, hostname, port=830, username=None, password=None,
105
    #            key_filename=None, timeout=None, allow_agent=True,
106
    #            look_for_keys=True):
107
    #    self._client.connect(hostname, port=port, username=username,
108
    #                        password=password, key_filename=key_filename,
109
    #                        timeout=timeout, allow_agent=allow_agent,
110
    #                        look_for_keys=look_for_keys)    
111
    #    transport = self._client.get_transport()
112
    #    self._channel = transport.open_session()
113
    #    self._channel.invoke_subsystem('netconf')
114
    #    self._channel.set_name('netconf')
115
    #    self._connected = True
116
    #    self._post_connect()
117

  
118
    def connect(self, hostname, port=830, username=None, password=None,
119
                key_filename=None, timeout=None, allow_agent=True,
120
                look_for_keys=True):
121
        self._transport = paramiko.Transport()
122
    
123
    def run(self):
124
        chan = self._channel
125
        chan.setblocking(0)
126
        q = self._q
127
        try:
128
            while True:    
129
                if chan.closed:
130
                    raise SessionCloseError(self._in_buf.getvalue())         
131
                if chan.send_ready() and not q.empty():
132
                    data = q.get() + SSHSession.MSG_DELIM
133
                    while data:
134
                        n = chan.send(data)
135
                        if n <= 0:
136
                            raise SessionCloseError(self._in_buf.getvalue(), data)
137
                        data = data[n:]
138
                if chan.recv_ready():
139
                    data = chan.recv(SSHSession.BUF_SIZE)
140
                    if data:
141
                        self._in_buf.write(data)
142
                        self._fresh_data()
143
                    else:
144
                        raise SessionCloseError(self._in_buf.getvalue())
145
        except Exception as e:
146
            logger.debug('*** broke out of main loop ***')
147
            self.dispatch('error', e)
148

  
149
class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
150
    
151
    def __init__(self, cb):
152
        self._cb = cb
153
    
154
    def missing_host_key(self, client, hostname, key):
155
        if not self._cb(hostname, key):
156
            raise SSHError
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Lock
16

  
17
import logging
18

  
19
logger = logging.getLogger('ncclient.subject')
20

  
21
class Subject:
22
        
23
    def __init__(self, listeners=[]):
24
        self._listeners = set(listeners)
25
        self._lock = Lock()
26
    
27
    def has_listener(self, listener):
28
        with self._lock:
29
            return (listener in self._listeners)
30
    
31
    def add_listener(self, listener):
32
        with self._lock:
33
            self._listeners.add(listener)
34
    
35
    def remove_listener(self, listener):
36
        with self._lock:
37
            self._listeners.discard(listener)
38
    
39
    def dispatch(self, event, *args, **kwds):
40
        # holding the lock while doing callbacks could lead to a deadlock
41
        # if one of the above methods is called
42
        with self._lock:
43
            listeners = list(self._listeners)
44
        for l in listeners:
45
            try:
46
                logger.debug('dispatching [%s] to [%s]' % (event, l))
47
                getattr(l, event)(*args, **kwds)
48
            except Exception as e:
49
                logger.warning(e)

Also available in: Unified diff