Revision d6688264

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
'''
16
NOTES
17
=====
18

  
19
- operations complete
20
- make operational again
21
- LockContext
22
- op specfic reply objects
23
- manager testing and augmenting
24
parse into dicts??
25

  
26
'''
27

  
15 28
import sys
16 29

  
17 30
if sys.version_info < (2, 6):
b/ncclient/capabilities.py
67 67
        if uri.startswith('urn:ietf:params:netconf:capability:'):
68 68
            return (':' + uri.split(':')[5])
69 69

  
70

  
71 70
CAPABILITIES = Capabilities([
72
    'urn:ietf:params:netconf:base:1.0', # TODO
73
    'urn:ietf:params:netconf:capability:writable-running:1.0', # TODO
74
    'urn:ietf:params:netconf:capability:candidate:1.0', # TODO
75
    'urn:ietf:params:netconf:capability:confirmed-commit:1.0', # TODO
76
    'urn:ietf:params:netconf:capability:rollback-on-error:1.0', # TODO
77
    'urn:ietf:params:netconf:capability:startup:1.0', # TODO
78
    'urn:ietf:params:netconf:capability:url:1.0', # TODO
79
    'urn:ietf:params:netconf:capability:validate:1.0', # TODO
80
    'urn:ietf:params:netconf:capability:xpath:1.0', # TODO
81
    'urn:ietf:params:netconf:capability:notification:1.0', # TODO
82
    'urn:ietf:params:netconf:capability:interleave:1.0' # TODO
83
    ])
71
    'urn:ietf:params:netconf:base:1.0',
72
    'urn:ietf:params:netconf:capability:writable-running:1.0',
73
    'urn:ietf:params:netconf:capability:candidate:1.0',
74
    'urn:ietf:params:netconf:capability:confirmed-commit:1.0',
75
    'urn:ietf:params:netconf:capability:rollback-on-error:1.0',
76
    'urn:ietf:params:netconf:capability:startup:1.0',
77
    'urn:ietf:params:netconf:capability:url:1.0',
78
    'urn:ietf:params:netconf:capability:validate:1.0',
79
    'urn:ietf:params:netconf:capability:xpath:1.0',
80
    'urn:ietf:params:netconf:capability:notification:1.0',
81
    'urn:ietf:params:netconf:capability:interleave:1.0'
82
])
b/ncclient/content.py
56 56
    def to_string(self, encoding='utf-8'):
57 57
        "TODO: docstring"
58 58
        xml = ET.tostring(self._root, encoding)
59
        # some etree versions don't always include xml decl e.g. with utf-8
59
        # some etree versions don't include xml decl with utf-8
60 60
        # this is a problem with some devices
61
        if not xml.startswith('<?xml'):
62
            return ((u'<?xml version="1.0" encoding="%s"?>'
61
        if encoding == 'utf-8':
62
            return ((u'<?xml version="1.0" encoding="utf-8"?>'
63 63
                     % encoding).encode(encoding) + xml)
64 64
        else:
65 65
            return xml
......
72 72
    @staticmethod
73 73
    def build(spec):
74 74
        "TODO: docstring"
75
        if 'tag' in spec:
75
        if ET.iselement(spec):
76
            return spec
77
        elif isinstance(spec, basestring):
78
            return ET.XML(spec)
79
        # assume isinstance(spec, dict)
80
        elif 'tag' in spec:
76 81
            ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
77
            ele.text = spec.get('text', '')
82
            ele.text = str(spec.get('text', ''))
78 83
            children = spec.get('children', [])
79
            if isinstance(children, dict):
80
                children = [children]
84
            if isinstance(children, dict): children = [children]
81 85
            for child in children:
82
                ele.append(TreeBuilder.build(child))
86
                ET.SubElement(ele, TreeBuilder.build(child))
83 87
            return ele
84
        elif 'xml' in spec:
85
            return ET.XML(spec['xml'])
86 88
        elif 'comment' in spec:
87 89
            return ET.Comment(spec.get('comment'))
88 90
        else:
b/ncclient/glue.py
15 15
"TODO: docstring"
16 16

  
17 17
from cStringIO import StringIO
18
from threading import Thread
19
from Queue import Queue
20
from threading import Lock
18
from threading import Thread, Lock
21 19
from xml.etree import cElementTree as ET
22 20

  
23 21
import logging
24 22
logger = logging.getLogger('ncclient.glue')
25 23

  
24

  
26 25
def parse_root(raw):
27 26
    '''Parse the top-level element from a string representing an XML document.
28 27
    
......
30 29
    the qualified name of the root element and `attributes` is an
31 30
    `{attribute: value}` dictionary.
32 31
    '''
33
    fp = StringIO(raw)
32
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
34 33
    for event, element in ET.iterparse(fp, events=('start',)):
35 34
        return (element.tag, element.attrib)
36 35

  
......
42 41
    def __init__(self):
43 42
        "TODO: docstring"
44 43
        Thread.__init__(self)
45
        self._q = Queue()
46 44
        self._listeners = set() # TODO(?) weakref
47 45
        self._lock = Lock()
48 46
    
49 47
    def _dispatch_message(self, raw):
50 48
        "TODO: docstring"
51
        root = parse_root(raw)
49
        try:
50
            root = parse_root(raw)
51
        except Exception as e:
52
            logger.error('error parsing dispatch message: %s' % e)
53
            return
52 54
        with self._lock:
53 55
            listeners = list(self._listeners)
54 56
        for l in listeners:
......
89 91
            for listener in self._listeners:
90 92
                if isinstance(listener, cls):
91 93
                    return listener
92
    
93
    def send(self, message):
94
        "TODO: docstring"
95
        logger.debug('queueing %s' % message)
96
        self._q.put(message)
97 94

  
98 95

  
99 96
class Listener(object):
b/ncclient/manager.py
44 44
        self._session.connect(*args, **kwds)
45 45
    
46 46
    def __getattr__(self, name):
47
        name = name.replace('_', '-')
47 48
        if name in OPERATIONS:
48 49
            return OPERATIONS[name](self._session).request
49 50
        else:
50 51
            raise AttributeError
52
    
53
    def get(self, *args, **kwds):
54
        g = operations.Get(self._session)
55
        reply = g.request(*args, **kwds)
56
        if reply.errors:
57
            raise RPCError(reply.errors)
58
        else:
59
            return reply.data
60
    
61
    def get_config(self, *args, **kwds):
62
        gc = operations.GetConfig(self._session)
63
        reply = gc.request(*args, **kwds)
64
        if reply.errors:
65
            raise RPCError(reply.errors)
66
        else:
67
            return reply.data
68

  
69
    def locked(self, target='running'):
70
        return LockContext(self._session, target)
b/ncclient/operations/edit.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
from ncclient.capabilities import URI
15 16
from ncclient.rpc import RPC
16 17

  
17
# TODO
18

  
19

  
20
'''
21
notes
22
-> editconfig and copyconfig <running> target depends on :writable-running
23
-> 
24

  
25
'''
18
import util
26 19

  
27 20
class EditConfig(RPC):
28 21
    
......
36 29
    def request(self):
37 30
        pass
38 31

  
39
class CopyConfig(RPC):
32

  
33
class DeleteConfig(RPC): # x
40 34
    
41 35
    SPEC = {
42
        
36
        'tag': 'delete-config',
37
        'children': [ { 'tag': 'target', 'children': None } ]
43 38
    }
44 39
    
45
    def request(self):
46
        pass
40
    def request(self, target=None, target_url=None):
41
        spec = DeleteConfig.SPEC.copy()
42
        spec['children'][0]['children'] = util.store_or_url(target, target_url)
43
        return self._request(spec)
44

  
47 45

  
48
class DeleteConfig(RPC):
46
class CopyConfig(RPC): # x
49 47
    
50 48
    SPEC = {
51
        'tag': 'delete-config',
49
        'tag': 'copy-config',
52 50
        'children': [
53
            'tag': 'target',
54
            'children': {'tag': None }
51
            { 'tag': 'source', 'children': {'tag': None } },
52
            { 'tag': 'target', 'children': {'tag': None } }
55 53
        ]
56 54
    }
57 55
    
58
    def request(self, target=None, targeturl=None):
59
        spec = deepcopy(DeleteConfig.SPEC)
60
        
56
    def request(self, source=None, source_url=None, target=None, target_url=None):
57
        spec = CopyConfig.SPEC.copy()
58
        spec['children'][0]['children'] = util.store_or_url(source, source_url)
59
        spec['children'][1]['children'] = util.store_or_url(target, target_url)
60
        return self._request(spec)
61 61

  
62
class Validate(RPC):
62

  
63
class Validate(RPC): # xxxxx
63 64
    
64
    DEPENDS = ['urn:ietf:params:netconf:capability:validate:1.0']
65
    SPEC = {}
65
    DEPENDS = [':validate']
66 66
    
67
    def request(self):
68
        pass
69

  
67
    SPEC = {
68
        'tag': 'validate',
69
        'children': []
70
    }
71
    
72
    def request(self, source=None, config=None):
73
        #self.either_or(source, config)
74
        #
75
        #if source is None and config is None:
76
        #    raise OperationError('Insufficient parameters')
77
        #if source is not None and config is not None:
78
        #    raise OperationError('Too many parameters')
79
        #spec = Validate.SPEC.copy()
80
        #
81
        util.one_of(source, capability)
82
        if source is not None:
83
            spec['children'].append({
84
                'tag': 'source',
85
                'children': {'tag': source}
86
                })
87
        #
88
        #else:
89
        #    if isinstance(config, dict):
90
        #        if config['tag'] != 'config':
91
        #            child['tag'] = 'config'
92
        #            child['children'] = config
93
        #        else:
94
        #            child = config
95
        #    elif isinstance(config, Element):
96
        #        pass
97
        #    else:
98
        #        from xml.etree import cElementTree as ET
99
        #        ele = ET.XML(unicode(config))
100
        #        if __(ele.tag) != 'config':
101
        #            pass
102
        #        else:
103
        #            pass
104
        #    spec['children'].append(child)
105
        #
106
        return self._request(spec)
70 107

  
71
class Commit(RPC):
108
class Commit(RPC): # x
72 109
    
73
    SPEC = {'tag': 'commit'}
110
    DEPENDS = [':candidate']
74 111
    
75
    def request(self):
112
    SPEC = {'tag': 'commit', 'children': [] }
113
    
114
    def _parse_hook(self):
115
        pass
116
    
117
    def request(self, confirmed=False, timeout=None):
118
        spec = SPEC.copy()
119
        if confirmed:
120
            self._assert(':confirmed-commit')
121
            children = spec['children']
122
            children.append({'tag': 'confirmed'})
123
            if timeout is not None:
124
                children.append({
125
                    'tag': 'confirm-timeout',
126
                    'text': timeout
127
                })
76 128
        return self._request(Commit.SPEC)
77 129

  
78 130

  
79
class DiscardChanges(RPC):
131
class DiscardChanges(RPC): # x
132
    
133
    DEPENDS = [':candidate']
80 134
    
81
    DEPENDS = ['urn:ietf:params:netconf:capability:candidate:1.0']
82 135
    SPEC = {'tag': 'discard-changes'}
83 136
    
84 137
    def request(self):
b/ncclient/operations/lock.py
20 20

  
21 21
# TODO - a context manager around some <target> would be real neat
22 22

  
23
class Lock(RPC):
23
class Lock(RPC): # x
24 24
    
25 25
    SPEC = {
26 26
        'tag': 'lock',
......
31 31
    }
32 32
    
33 33
    def request(self, target='running'):
34
        if target=='candidate':
35
            self._assert(':candidate')
34 36
        spec = deepcopy(Lock.SPEC)
35 37
        spec['children']['children']['tag'] = target
36 38
        return self._request(spec)
37 39

  
38 40

  
39
class Unlock(RPC):
41
class Unlock(RPC): # x
40 42
    
41 43
    SPEC = {
42 44
        'tag': 'unlock',
......
47 49
    }
48 50
    
49 51
    def request(self, target='running'):
52
        if target=='candidate':
53
            self._assert(':candidate')
50 54
        spec = deepcopy(Unlock.SPEC)
51 55
        spec['children']['children']['tag'] = target
52 56
        return self._request(self.spec)
57

  
58

  
59
class LockContext:
60
        
61
    def __init__(self, session, target='running'):
62
        self.session = session
63
        self.target = target
64
        
65
    def __enter__(self):
66
        Lock(self.session).request(self.target)
67
        return self
68
    
69
    def __exit__(self, t, v, tb):
70
        Unlock(self.session).request(self.target)
71
        return False
b/ncclient/operations/retrieve.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
from copy import deepcopy
16

  
17
from ncclient.rpc import RPC
15
from ncclient.rpc import RPC, RPCReply
18 16

  
19 17
def build_filter(spec, type, criteria):
20 18
    filter = {
21 19
        'tag': 'filter',
22 20
        'attributes': {'type': type}
23 21
    }
24
    if type=='subtree':
25
        if isinstance(criteria, dict):
26
            filter['children'] = [criteria]
27
        else:
28
            filter['text'] = criteria
29
    elif type=='xpath':
22
    if type == 'subtree':
23
        filter['children'] = [criteria]
24
    elif type == 'xpath':
30 25
        filter['attributes']['select'] = criteria
26
    return filter
31 27

  
32
class Get(RPC):
28
class Get(RPC): # xx
33 29
    
34 30
    SPEC = {
35 31
        'tag': 'get',
36 32
        'children': []
37 33
    }
38 34
    
35
    REPLY_CLS = GetReply
36
    
39 37
    def request(self, filter=None):
40
        spec = deepcopy(SPEC)
38
        spec = Get.SPEC.copy()
41 39
        if filter is not None:
40
            #if filter[0] == 'xpath':
41
            #    self._assert(':xpath')
42 42
            spec['children'].append(build_filter(*filter))
43 43
        return self._request(spec)
44 44

  
45
class GetReply(RPCReply):
46
    
47
    def parse(self):
48
        RPCReply.parse(self)
45 49

  
46
class GetConfig(RPC):
50
class GetConfig(RPC): # xx
47 51
    
48 52
    SPEC = {
49 53
        'tag': 'get-config',
50 54
        'children': [ { 'tag': 'source', 'children': {'tag': None } } ]
51 55
    }
52 56
    
53
    def request(self, source='running', filter=None):
54
        spec = deepcopy(SPEC)
55
        spec['children'][0]['children']['tag'] = source
57
    REPLY_CLS = GetConfigReply
58
    
59
    def request(self, source=None, source_url=None, filter=None):
60
        self._one_of(source, source_url)
61
        spec = GetConfig.SPEC.copy()
62
        if source is not None:
63
            spec['children'][0]['children']['tag'] = source
64
        if source_url is not None:
65
            #self._assert(':url')
66
            spec['children'][0]['children']['tag'] = 'url'
67
            spec['children'][0]['children']['text'] = source_url        
56 68
        if filter is not None:
69
            #if filter[0] == 'xpath':
70
            #    self._assert(':xpath')
57 71
            spec['children'].append(build_filter(*filter))
58 72
        return self._request(spec)
73

  
74
class GetReply(RPCReply):
75
    
76
    def parse(self):
77
        RPCReply.parse(self)
b/ncclient/operations/session.py
15 15
'Session-related NETCONF operations'
16 16

  
17 17
from ncclient.rpc import RPC
18
from copy import deepcopy
19 18

  
20
class CloseSession(RPC):
19
class CloseSession(RPC): # x
21 20
    
22 21
    'CloseSession is always synchronous'
23 22
    
......
32 31
        return self._request(CloseSession.SPEC)
33 32

  
34 33

  
35
class KillSession(RPC):
34
class KillSession(RPC): # x
36 35
    
37 36
    SPEC = {
38 37
        'tag': 'kill-session',
39
        'children': [ { 'tag': 'session-id', 'text': None} ]
38
        'children': { 'tag': 'session-id', 'text': None}
40 39
    }
41 40
    
42 41
    def request(self, session_id):
43 42
        if not isinstance(session_id, basestring): # just making sure...
44 43
            session_id = str(session_id)
45
        spec = deepcopy(SPEC)
44
        spec = KillSession.SPEC.copy()
46 45
        spec['children'][0]['text'] = session_id
47 46
        return self._request(spec)
b/ncclient/operations/util.py
1
#!/usr/bin/env python
2

  
3
'boilerplate'
4

  
5
from ncclient import OperationError
6

  
7
class MissingCapabilityError(OperationError):
8
    pass
9

  
10
def one_of(self, *args):
11
    for i, arg in enumerate(args):
12
        if arg is not None:
13
            for argh in args[i+1:]:
14
                if argh is not None:
15
                    raise OperationError('Too many parameters')
16
            else:
17
                return
18
    raise OperationError('Insufficient parameters')
19

  
20

  
21
def assert_capability(key, capabilities):
22
    if key not in capabilities:
23
        raise MissingCapabilityError
24

  
25

  
26
def store_or_url(store, url):
27
    one_of(store, url)
28
    node = {}
29
    if store is not None:
30
        node['tag'] = store
31
    else:
32
        node['tag'] = 'url'
33
        node['text'] = url
34
    return node
b/ncclient/rpc/__init__.py
15 15
from rpc import RPC
16 16
from reply import RPCReply
17 17

  
18
class ReplyTimeoutError(Exception):
19
    pass
18
from ncclient import RPCError
19

  
20
class ReplyTimeoutError(RPCError): pass
20 21

  
21 22
__all__ = [
22 23
    'RPC',
b/ncclient/rpc/reply.py
38 38
        if __(root.tag) != 'rpc-reply':
39 39
            raise ValueError('Root element is not RPC reply')
40 40
        
41
        ok = False
41 42
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
42 43
        oktags = _('ok')
43 44
        for oktag in oktags:
44 45
            if root.find(oktag) is not None:
45 46
                logger.debug('parsed [%s]' % oktag)
46
                self._parsed = True
47
                return
48
        
49
        # create RPCError objects from <rpc-error> elements
50
        errtags = _('rpc-error')
51
        for errtag in errtags:
52
            for err in root.getiterator(errtag): # a particular <rpc-error>
53
                logger.debug('parsed [%s]' % errtag)
54
                d = {}
55
                for err_detail in err.getchildren(): # <error-type> etc..
56
                    tag = __(err_detail.tag)
57
                    d[tag] = (err_detail.text.strip() if tag != 'error-info'
58
                              else ET.tostring(err_detail, 'utf-8'))
59
                self._errors.append(RPCError(d))
60
            if self._errors:
61 47
                break
48
        else:
49
            # create RPCError objects from <rpc-error> elements
50
            errtags = _('rpc-error')
51
            for errtag in errtags:
52
                for err in root.getiterator(errtag): # a particular <rpc-error>
53
                    logger.debug('parsed [%s]' % errtag)
54
                    d = {}
55
                    for err_detail in err.getchildren(): # <error-type> etc..
56
                        tag = __(err_detail.tag)
57
                        d[tag] = (err_detail.text.strip() if tag != 'error-info'
58
                                  else ET.tostring(err_detail, 'utf-8'))
59
                    self._errors.append(RPCError(d))
60
                if self._errors:
61
                    break
62
        
63
        if self.ok:
64
            # TODO: store children in some way...
65
            pass
62 66
        
63 67
        self._parsed = True
64 68
    
b/ncclient/rpc/rpc.py
27 27
import logging
28 28
logger = logging.getLogger('ncclient.rpc')
29 29

  
30

  
31 30
class RPC(object):
32 31
    
32
    DEPENDS = []
33
    REPLY_CLS = RPCReply
34
    
33 35
    def __init__(self, session, async=False, timeout=None):
34 36
        if not session.can_pipeline:
35 37
            raise UserWarning('Asynchronous mode not supported for this device/session')
36 38
        self._session = session
39
        try:
40
            for cap in self.DEPENDS:
41
                self.assert_capability(cap)
42
        except AttributeError:
43
            pass        
37 44
        self._async = async
38 45
        self._timeout = timeout
39 46
        self._id = uuid1().urn
......
51 58
            }
52 59
        return TreeBuilder(spec).to_string(encoding)
53 60
    
54
    def _request(self, op, timeout=None):
61
    def _request(self, op):
55 62
        req = self._build(op)
56 63
        self._session.send(req)
57 64
        if self._async:
58 65
            return self._reply_event
59 66
        else:
60
            self._reply_event.wait(timeout)
67
            self._reply_event.wait(self._timeout)
61 68
            if self._reply_event.isSet():
62 69
                self._reply.parse()
63 70
                return self._reply
......
68 75
        'For subclasses'
69 76
        pass
70 77
    
78
    def _assert(self, capability):
79
        if capability not in self._session.server_capabilities:
80
            raise MissingCapabilityError('Server does not support [%s]' % cap)
81
    
71 82
    def deliver(self, raw):
72
        self._reply = RPCReply(raw)
83
        self._reply = self.REPLY_CLS(raw)
73 84
        self._delivery_hook()
74 85
        self._reply_event.set()
75 86
    
b/ncclient/transport/session.py
13 13
# limitations under the License.
14 14

  
15 15
from threading import Event
16
from Queue import Queue
16 17

  
17 18
from ncclient.capabilities import Capabilities, CAPABILITIES
18 19
from ncclient.glue import Subject
......
30 31
        "Subclass constructor should call this"
31 32
        Subject.__init__(self)
32 33
        self.setName('session')
34
        self._q = Queue()
33 35
        self._client_capabilities = CAPABILITIES
34 36
        self._server_capabilities = None # yet
35 37
        self._id = None # session-id
......
71 73
        "Subclass implements"
72 74
        raise NotImplementedError
73 75
    
76
    def send(self, message):
77
        "TODO: docstring"
78
        logger.debug('queueing %s' % message)
79
        self._q.put(message)
80
    
74 81
    ### Properties
75 82
    
76 83
    @property
b/ncclient/util.py
14 14

  
15 15
from ncclient.glue import Listener
16 16

  
17
import logging
18
logger = logging.getLogger('PrintListener')
19

  
20 17
class PrintListener(Listener):
21 18
    
22 19
    def callback(self, root, raw):

Also available in: Unified diff