Revision 589b23e4

b/ncclient/__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
__version__ = "0.01"
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
from ..error import ClientError, NETCONFError
16

  
17
class ContentError(ClientError): pass
18

  
19
class ValidationError(NETCONFError): pass
b/ncclient/content/hello.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 xml.etree import cElementTree as ElementTree
17

  
18
logging.getLogger('ncclient.content.hello')
19

  
20
from ..capability import Capabilities
21

  
22
ns = 'urn:ietf:params:xml:ns:netconf:base:1.0'
23

  
24
def make(capabilities):
25
    return '<hello xmlns="%s">%s</hello>' % (ns, capabilities)
26

  
27
def parse(raw):
28
    id, capabilities = 0, Capabilities()
29
    hello = ElementTree.fromstring(raw)
30
    for child in hello.getchildren():
31
        if child.tag == '{%s}session-id' % ns:
32
            id = child.text
33
        elif child.tag == '{%s}capabilities' % ns:
34
            for cap in child.getiterator('{%s}capability' % ns):
35
                capabilities.add(cap.text)
36
    return id, capabilities
37

  
38
#class HelloParser:
39
#    
40
#    'Fast parsing with expat'
41
#    
42
#    capability, sid = range(2)
43
#    
44
#    def __init__(self, raw):
45
#        self._sid = None
46
#        self._capabilities = Capabilities()
47
#        p = xml.parsers.expat.ParserCreate()
48
#        p.StartElementHandler = self._start_element
49
#        p.EndElementHandler = self._end_element
50
#        p.CharacterDataHandler = self._char_data
51
#        self._expect = None
52
#        p.parse(raw, True)
53
#    
54
#    def _start_element(self, name, attrs):
55
#        if name == 'capability':
56
#            self._expect = HelloParser.capability
57
#        elif name == 'session-id':
58
#            self._expect = HelloParser.sid
59
#    
60
#    def _end_element(self, name):
61
#        self._expect = None
62
#    
63
#    def _char_data(self, data):
64
#        if self._expect == HelloParser.capability:
65
#            self._capabilities.add(data)
66
#        elif self._expect == HelloParser.sid:
67
#            self._sid = int(data)
68
#    
69
#    @property
70
#    def sid(self): return self._sid
71
#    
72
#    @property
73
#    def capabilities(self): return self._capabilities
b/ncclient/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
16

  
17
from listener import Listener
18

  
19
from content import MessageIDParser
20

  
21
class RPC:
22
    
23
    cur_id = {}
24

  
25
    def __init__(self, session=None, async=False):
26
        self._session = None
27
        self._async = None
28
        self._reply = None
29
        self._event = Event()
30
    
31
    def get_reply(self, timeout=2.0):
32
        self._event.wait(timeout)
33
        if self._event.isSet():
34
            return self._reply
35
    
36
    def do(self, session, async=False):
37
        self._async = async
38
    
39
    def deliver(self, reply):
40
        self._reply = reply
41
        self._event.set()
42

  
43
    @property
44
    def has_reply(self): return self._event.isSet()
45
    
46
    @property
47
    def async(self): return self._async
48
    
49
    @property
50
    def listener(self): return self._listener
51
    
52
    def _next_id(self):
53
        cur_id[self._sid] = cur_id.get(self._sid, 0) + 1
54
        return cur_id[self._sid]
55
    
56
class RPCReply:
57
    
58
    def __init__(self, raw):
59
        self._raw = raw
60
    
61
    def get_id(self):
62
        return content.rpc.parse_msg_id(raw)
63

  
64
class RPCError(NETCONFError):
65
    
66
    pass
67

  
68
class ReplyListener(Listener):
69
    
70
    def __init__(self):
71
        self._id2rpc = {}
72
    
73
    def reply(self, msg):
74
        reply = RPCReply(msg)
75
        id2rpc[reply.get_id()].deliver(reply)
76
    
77
    def error(self, buf):
78
        pass
b/ncclient/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

  
17
from threading import Thread, Event
18
from Queue import Queue
19

  
20
from error import ClientError
21
from content import hello
22
from listener import Subject
23
from capability import CAPABILITIES
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, listeners=[Session.HelloListener(self)])
34
        self._client_capabilities = CAPABILITIES
35
        self._server_capabilities = None # yet
36
        self._id = None # session-id
37
        self._connected = False
38
        self._error = None
39
        self._init_event = Event()
40
        self._q = Queue()
41
    
42
    def _close(self):
43
        self._connected = False
44
    
45
    def _init(self):
46
        self._connected = True
47
        # start the subclass' main loop
48
        self.start()
49
        # queue client's hello message for sending
50
        self.send(hello.make(self._client_capabilities))
51
        # we expect server's hello message, wait for _init_event to be set by HelloListener
52
        self._init_event.wait()
53
        # there may have been an error
54
        if self._error:
55
            self._close()
56
            raise self._error
57

  
58
    def connect(self):
59
        raise NotImplementedError
60

  
61
    def send(self, message):
62
        message = (u'<?xml version="1.0" encoding="UTF-8"?>%s' % message).encode('utf-8')
63
        logger.debug('queueing message: \n%s' % message)
64
        self._q.put(message)
65

  
66
    def run(self):
67
        raise NotImplementedError
68
    
69
    ### Properties
70

  
71
    @property
72
    def client_capabilities(self): return self._client_capabilities
73
    
74
    @property
75
    def serve_capabilities(self): return self._server_capabilities
76
    
77
    @property
78
    def connected(self): return self._connected
79
    
80
    @property
81
    def id(self): return self._id    
82

  
83
    class HelloListener:
84
        
85
        def __init__(self, session):
86
            self._session = session
87
        
88
        def _done(self, err=None):
89
            if err is not None:
90
                self._session._error = err
91
            self._session.remove_listener(self)
92
            self._session._init_event.set()
93
        
94
        ### Events
95
        
96
        def reply(self, data):
97
            err = None
98
            try:
99
                id, capabilities = hello.parse(data)
100
                logger.debug('session_id: %s | capabilities: \n%s', id, capabilities)
101
                self._session._id, self._session.capabilities = id, capabilities
102
            except Exception as e:
103
                err = e
104
            finally:
105
                self._done(err)
106
        
107
        def close(self, err):
108
            self._done(err)
b/ncclient/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
import paramiko
17

  
18
from session import Session, SessionError
19

  
20
logger = logging.getLogger('ncclient.ssh')
21

  
22
class SessionCloseError(SessionError):
23
    
24
    def __str__(self):
25
        return 'RECEIVED: %s | UNSENT: %s' % (self._in_buf, self._out_buf)
26
    
27
    def __init__(self, in_buf, out_buf):
28
        SessionError.__init__(self)
29
        self._in_buf, self._out_buf = in_buf, out_buf
30

  
31
class SSHSession(Session):
32

  
33
    BUF_SIZE = 4096
34
    MSG_DELIM = ']]>]]>'
35
    
36
    def __init__(self, load_known_hosts=True,
37
                 missing_host_key_policy=paramiko.RejectPolicy):
38
        Session.__init__(self)
39
        self._in_buf = ''
40
        self._out_buf = ''
41
        self._client = paramiko.SSHClient()
42
        if load_known_hosts:
43
            self._client.load_system_host_keys()
44
        self._client.set_missing_host_key_policy(missing_host_key_policy)
45
    
46
    def load_host_keys(self, filename):
47
        self._client.load_host_keys(filename)
48
    
49
    def set_missing_host_key_policy(self, policy):
50
        self._client.set_missing_host_key_policy(policy)
51
    
52
    # paramiko exceptions ok?
53
    # user might be looking for ClientError
54
    def connect(self, hostname, port=830, username=None, password=None,
55
                key_filename=None, timeout=None, allow_agent=True,
56
                look_for_keys=True):
57
        self._client.connect(hostname, port=port, username=username,
58
                            password=password, key_filename=key_filename,
59
                            timeout=timeout, allow_agent=allow_agent,
60
                            look_for_keys=look_for_keys)    
61
        transport = self._client.get_transport()
62
        self._channel = transport.open_session()
63
        self._channel.invoke_subsystem('netconf')
64
        self._channel.set_name('netconf')
65
        self._init()
66

  
67
    def _close(self):
68
        self._channel.close()
69
        Session._close(self)
70
    
71
    def run(self):
72
        
73
        chan = self._channel
74
        chan.setblocking(0)
75
        q = self._q
76
        
77
        while True:
78
            
79
            if chan.closed:
80
                break
81
            
82
            if chan.recv_ready():
83
                data = chan.recv(SSHSession.BUF_SIZE)
84
                if data:
85
                    self._in_buf += data
86
                    while True:
87
                        before, delim, after = self._in_buf.partition(SSHSession.MSG_DELIM)
88
                        if delim:
89
                            self.dispatch('reply', before)
90
                            self._in_buf = after
91
                        else:
92
                            break
93
                else:
94
                    break
95
            
96
            if chan.send_ready():
97
                if not q.empty():
98
                    msg = q.get()
99
                    self._out_buf += ( msg + SSHSession.MSG_DELIM )
100
                    while self._out_buf:
101
                        n = chan.send(self._out_buf)
102
                        if n <= 0:
103
                            break
104
                        self._out_buf = self._out_buf[n:]
105
        
106
        logger.debug('** broke out of main loop **')
107
        self.dispatch('close', SessionCloseError(self._in_buf, self._out_buf))
108

  
109
class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
110
    
111
    def __init__(self, cb):
112
        self._cb = cb
113
    
114
    def missing_host_key(self, client, hostname, key):
115
        if not self._cb(hostname, key):
116
            raise SSHError

Also available in: Unified diff