Revision a956ef07

b/ncclient/__init__.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
__version__ = "0.01"
15
import sys
16

  
17
if sys.version_info < (2, 5):
18
    raise RuntimeError('You need python 2.5 for this module.')
19

  
20
__version__ = "0.05"
/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
class ClientError(Exception):
16
    
17
    pass
18

  
19
class NETCONFError(ClientError):
20
    
21
    pass
b/ncclient/operations/__init__.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
'NETCONF Remote Procedure Calls (RPC) and protocol operations'
16

  
15 17
from ncclient import content
16
from ncclient import rpc
17 18
from ncclient.capabilities import CAPABILITIES
19
from rpc import RPC, RPCReply, RPCError
18 20

  
19 21
from retrieve import Get, GetConfig
20 22
from edit import EditConfig, DeleteConfig
b/ncclient/operations/listener.py
1
#!/usr/bin/env python
2

  
3
_listeners = WeakValueDictionary()
4

  
5
def get_listener(session):
6
    try:
7
        return _listeners[session]
8
    except KeyError:
9
        _listeners[session] = MessageListener()
10
        return _listeners[session]
11

  
12
class MessageListener:
13
    
14
    def __init__(self):
15
        # {message-id: RPC}
16
        self._rpc = WeakValueDictionary()
17
        # if the session gets closed by remote endpoint,
18
        # need to know if it is an error event or was requested through
19
        # a NETCONF operation i.e. CloseSession
20
        self._expecting_close = False
21
        # other recognized names and behavior on receiving them
22
        self._recognized = []
23
    
24
    def __str__(self):
25
        return 'MessageListener'
26
    
27
    def expect_close(self):
28
        self._expecting_close = True
29
    
30
    def register(self, id, op):
31
        self._id2rpc[id] = op
32
    
33
    ### Events
34
    
35
    def reply(self, raw):
36
        pass
37
    
38
    def error(self, err):
39
        from ncclient.session.session import SessionCloseError
40
        if err is SessionCloseError:
41
            logger.debug('session closed by remote endpoint, expecting_close=%s' %
42
                         self._expecting_close)
43
            if not self._expecting_close:
44
                raise err
b/ncclient/operations/rpc.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
'Remote Procedure Call'
16

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

  
18
import content
19
from listeners import session_listener_factory
20
_listeners = WeakValueDictionary()
21

  
22
def get_listener(session):
23
    try:
24
        return _listeners[session]
25
    except KeyError:
26
        _listeners[session] = MessageListener()
27
        return _listeners[session]
20 28

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

  
38 40
    def _response_cb(self, reply):
39 41
        self._reply = reply
......
69 71
        return self._id
70 72
    
71 73
    @property
74
    def listener(self):
75
        listener = get_listener(self._session)
76

  
77
    @property
72 78
    def session(self):
73 79
        return self._session
74 80

  
......
76 82
    
77 83
    class RPCError:
78 84
        
79
        pass
85
        pass
86
    
87

  
88
class MessageListener:
89
    
90
    def __init__(self):
91
        # {message-id: RPC}
92
        self._rpc = WeakValueDictionary()
93
        # if the session gets closed by remote endpoint,
94
        # need to know if it is an error event or was requested through
95
        # a NETCONF operation i.e. CloseSession
96
        self._expecting_close = False
97
        # other recognized names and behavior on receiving them
98
        self._recognized = []
99
    
100
    def __str__(self):
101
        return 'MessageListener'
102
    
103
    def expect_close(self):
104
        self._expecting_close = True
105
    
106
    def register(self, id, op):
107
        self._id2rpc[id] = op
108
    
109
    ### Events
110
    
111
    def reply(self, raw):
112
        pass
113
    
114
    def error(self, err):
115
        from ncclient.session.session import SessionCloseError
116
        if err is SessionCloseError:
117
            logger.debug('session closed by remote endpoint, expecting_close=%s' %
118
                         self._expecting_close)
119
            if not self._expecting_close:
120
                raise err
121

  
b/ncclient/operations/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
'Session-related NETCONF operations'
16

  
17
class CloseSession(RPC):
18
    
19
    pass
20

  
21
class KillSession(RPC):
22
    
23
    pass
b/ncclient/session/__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
from session import SessionError, SessionCloseError
16
from ssh import SSHSession
17

  
18
__all__ = [
19
    'SSHSession',
20
    'SessionError',
21
    'SessionCloseError',
22
]
/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/session/session.py
16 16
from threading import Thread, Event
17 17
from Queue import Queue
18 18

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

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

  
27
class SessionError(ClientError): pass
23
class SessionError(Exception):
24
    
25
    pass
26

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

  
29
class Session(Thread, Subject):
36
class Session(Thread):
30 37
    
31 38
    def __init__(self):
32 39
        Thread.__init__(self, name='session')
33
        Subject.__init__(self)
34 40
        self._client_capabilities = CAPABILITIES
35 41
        self._server_capabilities = None # yet
36 42
        self._id = None # session-id
37
        self._error = None
38
        self._init_event = Event()
39 43
        self._q = Queue()
40 44
        self._connected = False # to be set/cleared by subclass implementation
45
        self._listeners = set(listeners)
46
        self._lock = Lock()
41 47
    
42 48
    def _post_connect(self):
43
        # start the subclass' main loop
44
        listener = HelloListener(self)
45
        self.add_listener(listener)
46
        self.start()
49
        from ncclient.content.builders import HelloBuilder
47 50
        # 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.send(HelloBuilder.build(self._client_capabilities))
52
        
53
        error = None
54
        proceed = Event()
55
        def ok_cb(id, capabilities):
56
            self._id, self._capabilities = id, Capabilities(capabilities)
57
            proceed.set()
58
        def err_cb(err):
59
            error = err
60
            proceed.set()
61
        listener = HelloListener(ok_cb, err_cb)
62
        self.add_listener(listener)
63
        
64
        # start the subclass' main loop
65
        self.start()        
66
        # we expect server's hello message
67
        proceed.wait()
68
        # received hello message or an error happened
51 69
        self.remove_listener(listener)
52
        # there may have been an error
53 70
        if self._error:
54 71
            self._close()
55 72
            raise self._error
56 73
    
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 74
    def send(self, message):
66 75
        logger.debug('queueing message: \n%s' % message)
67 76
        self._q.put(message)
......
78 87
        elif whose == 'server':
79 88
            return self._server_capabilities
80 89
    
90
    ### Session is a subject for arbitary listeners
91
    
92
    def has_listener(self, listener):
93
        with self._lock:
94
            return (listener in self._listeners)
95
    
96
    def add_listener(self, listener):
97
        with self._lock:
98
            self._listeners.add(listener)
99
    
100
    def remove_listener(self, listener):
101
        with self._lock:
102
            self._listeners.discard(listener)
103
    
104
    def dispatch(self, event, *args, **kwds):
105
        # holding the lock while doing callbacks could lead to a deadlock
106
        # if one of the above methods is called
107
        with self._lock:
108
            listeners = list(self._listeners)
109
        for l in listeners:
110
            try:
111
                logger.debug('dispatching [%s] to [%s]' % (event, l))
112
                getattr(l, event)(*args, **kwds)
113
            except Exception as e:
114
                logger.warning(e)
115
    
81 116
    ### Properties
82 117
    
83 118
    @property
......
95 130
    @property
96 131
    def id(self):
97 132
        return self._id
133

  
134

  
135
class HelloListener:
136
    
137
    def __init__(self, init_cb, error_cb):
138
        self._init_cb, self._error_cb = reply_cb, error_cb
139
    
140
    def __str__(self):
141
        return 'HelloListener'
142
    
143
    ### Events
144
    
145
    def reply(self, raw):
146
        from ncclient.content.parsers import HelloParser
147
        try:
148
            id, capabilities = HelloParser.parse(raw)
149
        except Exception as e:
150
            self._error_cb(e)
151
        else:
152
            self._init_cb(id, capabilities)
153
    
154
    def error(self, err):
155
        self._error_cb(err)
b/ncclient/session/ssh.py
19 19

  
20 20
import paramiko
21 21

  
22

  
23
from session import Session, SessionError
22
from session import Session, SessionError, SessionCloseError
24 23

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

  
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

  
26
BUF_SIZE = 4096
27
MSG_DELIM = ']]>]]>'
37 28

  
38 29
class SSHSession(Session):
39 30

  
40
    BUF_SIZE = 4096
41
    MSG_DELIM = ']]>]]>'
42
    
43 31
    def __init__(self, load_known_hosts=True,
44 32
                 missing_host_key_policy=paramiko.RejectPolicy()):
45 33
        Session.__init__(self)
......
57 45
        self._connected = False
58 46
    
59 47
    def _fresh_data(self):
60
        delim = SSHSession.MSG_DELIM
48
        delim = MSG_DELIM
61 49
        n = len(delim) - 1
62 50
        state = self._parsing_state
63 51
        buf = self._in_buf
......
95 83
        self._parsing_state = state
96 84
        self._parsing_pos = self._in_buf.tell()
97 85

  
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()
86
    def load_host_keys(self, filename):
87
        self._client.load_host_keys(filename)
88

  
89
    def set_missing_host_key_policy(self, policy):
90
        self._client.set_missing_host_key_policy(policy)
117 91

  
118 92
    def connect(self, hostname, port=830, username=None, password=None,
119 93
                key_filename=None, timeout=None, allow_agent=True,
120 94
                look_for_keys=True):
121
        self._transport = paramiko.Transport()
95
        self._client.connect(hostname, port=port, username=username,
96
                            password=password, key_filename=key_filename,
97
                            timeout=timeout, allow_agent=allow_agent,
98
                            look_for_keys=look_for_keys)    
99
        transport = self._client.get_transport()
100
        self._channel = transport.open_session()
101
        self._channel.invoke_subsystem('netconf')
102
        self._channel.set_name('netconf')
103
        self._connected = True
104
        self._post_connect()
105
    
122 106
    
123 107
    def run(self):
124 108
        chan = self._channel
125 109
        chan.setblocking(0)
126 110
        q = self._q
127 111
        try:
128
            while True:    
112
            while True:
129 113
                if chan.closed:
130 114
                    raise SessionCloseError(self._in_buf.getvalue())         
131 115
                if chan.send_ready() and not q.empty():
132
                    data = q.get() + SSHSession.MSG_DELIM
116
                    data = q.get() + MSG_DELIM
133 117
                    while data:
134 118
                        n = chan.send(data)
135 119
                        if n <= 0:
136 120
                            raise SessionCloseError(self._in_buf.getvalue(), data)
137 121
                        data = data[n:]
138 122
                if chan.recv_ready():
139
                    data = chan.recv(SSHSession.BUF_SIZE)
123
                    data = chan.recv(BUF_SIZE)
140 124
                    if data:
141 125
                        self._in_buf.write(data)
142 126
                        self._fresh_data()
......
145 129
        except Exception as e:
146 130
            logger.debug('*** broke out of main loop ***')
147 131
            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