Revision dd225c7a

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

  
15
_capability_map = {
16
    'urn:liberouter:params:netconf:capability:power-control:1.0':
17
        [':power-control', ':power-control:1.0']
18
}
19

  
15 20
def _abbreviate(uri):
16 21
    if uri.startswith('urn:ietf:params:netconf:'):
17 22
        splitted = uri.split(':')
......
19 24
            return [ ':' + splitted[5], ':' + splitted[5] + ':' + splitted[6] ]
20 25
        elif ':base:' in uri:
21 26
            return [ ':base', ':base' + ':'+ splitted[5] ]
22
        else:
23
            return []
24
    else:
25
        return []
27
    elif uri in _capability_map:
28
        return _capability_map[uri]
29
    return []
26 30

  
27 31
def schemes(url_uri):
28 32
    """Given a URI that has a *scheme* query string (i.e. *:url* capability
......
87 91
        """
88 92
        return key in self
89 93

  
90
    def get_uris(self, shorthand):
91
        return [uri for uri, abbrs in self._dict.items() if shorthand in abbrs]
94
    def get_uri(self, shorthand):
95
        for uri, abbrs in self._dict.items():
96
            if shorthand in abbrs:
97
                return uri
92 98

  
93 99
#: :class:`Capabilities` object representing the capabilities currently supported by NCClient
94 100
CAPABILITIES = Capabilities([
......
101 107
    'urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp',
102 108
    'urn:ietf:params:netconf:capability:validate:1.0',
103 109
    'urn:ietf:params:netconf:capability:xpath:1.0',
110
    'urn:liberouter:params:netconf:capability:power-control:1.0'
104 111
    #'urn:ietf:params:netconf:capability:notification:1.0', # TODO
105 112
    #'urn:ietf:params:netconf:capability:interleave:1.0' # theoretically already supported
106 113
])
b/ncclient/manager.py
23 23

  
24 24
def connect_ssh(*args, **kwds):
25 25
    """Connect to NETCONF server over SSH. See :meth:`SSHSession.connect()
26
    <ncclient.transport.SSHSession.connect>` for function signature."""
26
    <ncclient.transport.SSHSession.connect>` for argument details.
27

  
28
    :rtype: :class:`Manager`
29
    """
27 30
    session = transport.SSHSession(capabilities.CAPABILITIES)
28 31
    session.load_known_hosts()
29 32
    session.connect(*args, **kwds)
......
32 35
#: Same as :meth:`connect_ssh`
33 36
connect = connect_ssh
34 37

  
35
#: Raise all :class:`~ncclient.operations.rpc.RPCError`
36
RAISE_ALL = 0
37
#: Only raise when *error-severity* is "error" i.e. no warnings
38
RAISE_ERR = 1
39
#: Don't raise any
40
RAISE_NONE = 2
41

  
42
class Manager:
38
class Manager(object):
43 39

  
44
    """API for NETCONF operations. Currently only supports making synchronous
45
    RPC requests.
40
    """API for NETCONF operations.
46 41

  
47 42
    It is also a context manager, so a :class:`Manager` instance can be used
48 43
    with the *with* statement. The session is closed when the context ends. """
49 44

  
50 45
    def __init__(self, session):
51 46
        self._session = session
52
        self._raise = RAISE_ALL
53

  
54
    def set_rpc_error_action(self, action):
55
        """Specify the action to take when an *<rpc-error>* element is encountered.
56

  
57
        :arg action: one of :attr:`RAISE_ALL`, :attr:`RAISE_ERR`, :attr:`RAISE_NONE`
58
        """
59
        self._raise = action
60 47

  
61 48
    def __enter__(self):
62 49
        return self
......
65 52
        self.close()
66 53
        return False
67 54

  
68
    def do(self, op, *args, **kwds):
69
        op = operations.OPERATIONS[op](self._session)
70
        reply = op.request(*args, **kwds)
71
        if not reply.ok:
72
            if self._raise == RAISE_ALL:
73
                raise reply.error
74
            elif self._raise == RAISE_ERR:
75
                for error in reply.errors:
76
                    if error.severity == 'error':
77
                        raise error
78
        return reply
79

  
80
    #: :see: :meth:`Get.request() <ncclient.operations.Get.request>`
81
    get = lambda self, *args, **kwds: self.do('get', *args, **kwds)
82

  
83
    #: :see: :meth:`GetConfig.request() <ncclient.operations.GetConfig.request>`
84
    get_config = lambda self, *args, **kwds: self.do('get-config', *args, **kwds)
85

  
86
    #: :see: :meth:`EditConfig.request() <ncclient.operations.EditConfig.request>`
87
    edit_config = lambda self, *args, **kwds: self.do('edit-config', *args, **kwds)
88

  
89
    #: :see: :meth:`CopyConfig.request() <ncclient.operations.CopyConfig.request>`
90
    copy_config = lambda self, *args, **kwds: self.do('copy-config', *args, **kwds)
91

  
92
    #: :see: :meth:`GetConfig.request() <ncclient.operations.Validate.request>`
93
    validate = lambda self, *args, **kwds: self.do('validate', *args, **kwds)
94

  
95
    #: :see: :meth:`Commit.request() <ncclient.operations.Commit.request>`
96
    commit = lambda self, *args, **kwds: self.do('commit', *args, **kwds)
97

  
98
    #: :see: :meth:`DiscardChanges.request() <ncclient.operations.DiscardChanges.request>`
99
    discard_changes = lambda self, *args, **kwds: self.do('discard-changes', *args, **kwds)
100

  
101
    #: :see: :meth:`DeleteConfig.request() <ncclient.operations.DeleteConfig.request>`
102
    delete_config = lambda self, *args, **kwds: self.do('delete-config', *args, **kwds)
103

  
104
    #: :see: :meth:`Lock.request() <ncclient.operations.Lock.request>`
105
    lock = lambda self, *args, **kwds: self.do('lock', *args, **kwds)
106

  
107
    #: :see: :meth:`DiscardChanges.request() <ncclient.operations.Unlock.request>`
108
    unlock = lambda self, *args, **kwds: self.do('unlock', *args, **kwds)
109

  
110
    #: :see: :meth:`CloseSession.request() <ncclient.operations.CloseSession.request>`
111
    close_session = lambda self, *args, **kwds: self.do('close-session', *args, **kwds)
112

  
113
    #: :see: :meth:`KillSession.request() <ncclient.operations.KillSession.request>`
114
    kill_session = lambda self, *args, **kwds: self.do('kill-session', *args, **kwds)
55
    def __getattr__(self, name):
56
        try:
57
            return operations.INDEX[name](self.session).request
58
        except KeyError:
59
            raise AttributeError
115 60

  
116 61
    def locked(self, target):
117 62
        """Returns a context manager for the *with* statement.
b/ncclient/operations/__init__.py
14 14

  
15 15
from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
16 16
from rpc import RPC, RPCReply, RPCError
17

  
18
# rfc4741 ops
17 19
from retrieve import Get, GetConfig, GetReply
18 20
from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges
19 21
from session import CloseSession, KillSession
20 22
from lock import Lock, Unlock, LockContext
21
#from subscribe import CreateSubscription
23
# others...
24
from flowmon import PoweroffMachine, RebootMachine
22 25

  
23
OPERATIONS = {
26
INDEX = {
24 27
    'get': Get,
25
    'get-config': GetConfig,
26
    'edit-config': EditConfig,
27
    'copy-config': CopyConfig,
28
    'get_config': GetConfig,
29
    'edit_config': EditConfig,
30
    'copy_config': CopyConfig,
28 31
    'validate': Validate,
29 32
    'commit': Commit,
30
    'discard-changes': DiscardChanges,
31
    'delete-config': DeleteConfig,
33
    'discard_changes': DiscardChanges,
34
    'delete_config': DeleteConfig,
32 35
    'lock': Lock,
33 36
    'unlock': Unlock,
34
    'close-session': CloseSession,
35
    'kill-session': KillSession,
37
    'close_session': CloseSession,
38
    'kill_session': KillSession,
39
    'poweroff_machine': PoweroffMachine,
40
    'reboot_machine': RebootMachine
36 41
}
37 42

  
38 43
__all__ = [
......
51 56
    'DeleteConfig',
52 57
    'Lock',
53 58
    'Unlock',
59
    'PoweroffMachine',
60
    'RebootMachine',
54 61
    'LockContext',
55 62
    'CloseSession',
56 63
    'KillSession',
b/ncclient/operations/edit.py
27 27

  
28 28
class EditConfig(RPC):
29 29

  
30
    # TESTED
31

  
32 30
    "*<edit-config>* RPC"
33 31

  
34 32
    SPEC = {'tag': 'edit-config', 'subtree': []}
......
79 77

  
80 78
class DeleteConfig(RPC):
81 79

  
82
    # TESTED
83

  
84 80
    "*<delete-config>* RPC"
85 81

  
86 82
    SPEC = {'tag': 'delete-config', 'subtree': []}
......
99 95

  
100 96
class CopyConfig(RPC):
101 97

  
102
    # TESTED
103

  
104 98
    "*<copy-config>* RPC"
105 99

  
106 100
    SPEC = {'tag': 'copy-config', 'subtree': []}
......
123 117

  
124 118
class Validate(RPC):
125 119

  
126
    # TESTED
127

  
128 120
    "*<validate>* RPC. Depends on the *:validate* capability."
129 121

  
130 122
    DEPENDS = [':validate']
......
153 145

  
154 146
class Commit(RPC):
155 147

  
156
    # TESTED
157

  
158 148
    "*<commit>* RPC. Depends on the *:candidate* capability."
159 149

  
160 150
    DEPENDS = [':candidate']
b/ncclient/operations/lock.py
16 16

  
17 17
from copy import deepcopy
18 18

  
19
from rpc import RPC, RPCReply, RPCError
19
from rpc import RPC
20 20

  
21
#class LockReply(RPCReply):
22
#
23
#    ERROR_CLS = LockDeniedError
24
#
25
#class LockDeniedError(RPCError):
26
#
27
#    def __new__(cls, err_dict):
28
#        if rpcerr['tag'] != 'lock-denied':
29
#            return RPCError(err_dict)
30
#        else:
31
#            return object.__new__(LockDeniedError)
32
#
33
#    def __init__(self, err_dict):
34
#        RPCError.__init__(self, err_dict)
21
# TODO:
22
# should have some way to parse session-id from a lock-denied error
35 23

  
36 24
class Lock(RPC):
37 25

  
38
    # TESTED
39

  
40 26
    "*<lock>* RPC"
41 27

  
42 28
    SPEC = {
......
63 49

  
64 50
class Unlock(RPC):
65 51

  
66
    # TESTED
67

  
68 52
    "*<unlock>* RPC"
69 53

  
70 54
    SPEC = {
......
89 73

  
90 74
class LockContext:
91 75

  
92
    # TESTED
93

  
94 76
    """
95 77
    A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's.
96 78

  
b/ncclient/operations/retrieve.py
21 21

  
22 22
class GetReply(RPCReply):
23 23

  
24
    # TESTED
25

  
26 24
    """Adds attributes for the *<data>* element to :class:`RPCReply`, which
27 25
    pertains to the :class:`Get` and :class:`GetConfig` operations."""
28 26

  
29 27
    def _parsing_hook(self, root):
30 28
        self._data = None
31 29
        if not self._errors:
32
            self._data = xml_.find(root, 'data',
33
                                      nslist=[xml_.BASE_NS,
34
                                              xml_.CISCO_BS])
30
            self._data = xml_.find(root, 'data', nslist=xml_.NSLIST)
35 31

  
36 32
    @property
37 33
    def data_ele(self):
......
58 54

  
59 55
class Get(RPC):
60 56

  
61
    # TESTED
62

  
63 57
    "The *<get>* RPC"
64 58

  
65 59
    SPEC = {'tag': 'get', 'subtree': []}
......
80 74

  
81 75
class GetConfig(RPC):
82 76

  
83
    # TESTED
84

  
85 77
    "The *<get-config>* RPC"
86 78

  
87 79
    SPEC = {'tag': 'get-config', 'subtree': []}
b/ncclient/operations/rpc.py
57 57
            return
58 58
        root = self._root = xml_.xml2ele(self._raw) # <rpc-reply> element
59 59
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
60
        ok = xml_.find(root, 'ok', nslist=[xml_.BASE_NS, xml_.CISCO_BS])
60
        ok = xml_.find(root, 'ok', nslist=xml_.NSLIST)
61 61
        if ok is not None:
62 62
            logger.debug('parsed [%s]' % ok.tag)
63 63
        else: # create RPCError objects from <rpc-error> elements
64
            error = xml_.find(root, 'rpc-error', nslist=[xml_.BASE_NS, xml_.CISCO_BS])
64
            error = xml_.find(root, 'rpc-error', nslist=xml_.NSLIST)
65 65
            if error is not None:
66 66
                logger.debug('parsed [%s]' % error.tag)
67 67
                for err in root.getiterator(error.tag):
......
272 272
        spec = {
273 273
            'tag': 'rpc',
274 274
            'attrib': {
275
                'xmlns': xml_.BASE_NS,
275
                'xmlns': xml_.BASE_NS_1_0,
276 276
                'message-id': self._id
277 277
                },
278 278
            'subtree': [ opspec ]
......
314 314
    def request(self, *args, **kwds):
315 315
        """Subclasses implement this method. Here, the operation is constructed
316 316
        in :ref:`dtree`, and the result of :meth:`_request` returned."""
317
        raise NotImplementedError
317
        return self._request(self.SPEC)
318 318

  
319 319
    def _delivery_hook(self):
320 320
        """Subclasses can implement this method. Will be called after
b/ncclient/operations/session.py
20 20

  
21 21
class CloseSession(RPC):
22 22

  
23
    # TESTED
24

  
25 23
    "*<close-session>* RPC. The connection to NETCONF server is also closed."
26 24

  
27 25
    SPEC = { 'tag': 'close-session' }
......
29 27
    def _delivery_hook(self):
30 28
        self.session.close()
31 29

  
32
    def request(self):
33
        ":seealso: :ref:`return`"
34
        return self._request(CloseSession.SPEC)
35

  
36 30

  
37 31
class KillSession(RPC):
38 32

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

  
15
from rpc import RPC
15
class Notification:
16
    pass
16 17

  
17
#from ncclient.xml import qualify as _
18
#from ncclient.transport import SessionListener
19
#
20
#NOTIFICATION_NS = 'urn:ietf:params:xml:ns:netconf:notification:1.0'
21
#
22
## TODO when can actually test it...
23
#
24
#class CreateSubscription(RPC):
25
#    # tested: no
26
#
27
#    SPEC = {
28
#        'tag': _('create-subscription', NOTIFICATION_NS)
29
#    }
30
#
31
#class Notification: pass
32
#
33
#class NotificationListener(SessionListener): pass
18
class CreateSubscription:
19
    pass
20

  
21
class NotificationListener:
22
    pass
b/ncclient/transport/session.py
227 227
        "Given a list of capability URI's returns <hello> message XML string"
228 228
        spec = {
229 229
            'tag': 'hello',
230
            'attrib': {'xmlns': xml_.BASE_NS},
230
            'attrib': {'xmlns': xml_.BASE_NS_1_0},
231 231
            'subtree': [{
232 232
                'tag': 'capabilities',
233 233
                'subtree': # this is fun :-)
b/ncclient/xml_.py
30 30
### Namespace-related
31 31

  
32 32
#: Base NETCONF namespace
33
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
33
BASE_NS_1_0 = 'urn:ietf:params:xml:ns:netconf:base:1.0'
34 34
#: ... and this is BASE_NS according to Cisco devices tested
35
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
35
CISCO_BS_1_0 = 'urn:ietf:params:netconf:base:1.0'
36 36
#: namespace for Tail-f data model
37 37
TAILF_AAA_1_1 = 'http://tail-f.com/ns/aaa/1.1'
38 38
#: namespace for Tail-f data model
39 39
TAILF_EXECD_1_1 = 'http://tail-f.com/ns/execd/1.1'
40 40
#: namespace for Cisco data model
41
CISCO_CPI_10 = 'http://www.cisco.com/cpi_10/schema'
41
CISCO_CPI_1_0 = 'http://www.cisco.com/cpi_10/schema'
42
#: namespace for Flowmon data model
43
FLOWMON_1_0 = 'http://www.liberouter.org/ns/netopeer/flowmon/1.0'
42 44

  
43 45
try:
44 46
    register_namespace = ET.register_namespace
......
48 50
        # cElementTree uses ElementTree's _namespace_map, so that's ok
49 51
        ElementTree._namespace_map[uri] = prefix
50 52

  
51
register_namespace('netconf', BASE_NS)
52
register_namespace('aaa', TAILF_AAA_1_1)
53
register_namespace('execd', TAILF_EXECD_1_1)
54
register_namespace('cpi', CISCO_CPI_10)
53
prefix_map = {
54
    BASE_NS_1_0: 'nc',
55
    TAILF_AAA_1_1: 'aaa',
56
    TAILF_EXECD_1_1: 'execd',
57
    CISCO_CPI_1_0: 'cpi',
58
    FLOWMON_1_0: 'fm',
59
}
55 60

  
61
for (ns, pre) in prefix_map.items():
62
    register_namespace(pre, ns)
56 63

  
57
qualify = lambda tag, ns=BASE_NS: tag if ns is None else '{%s}%s' % (ns, tag)
64
qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else '{%s}%s' % (ns, tag)
58 65

  
59
#: Deprecated
60
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
66
multiqualify = lambda tag, nslist=(BASE_NS_1_0, CISCO_BS_1_0): [qualify(tag, ns) for ns in nslist]
61 67

  
62 68
unqualify = lambda tag: tag[tag.rfind('}')+1:]
63 69

  
64
### XML with Python data structures
70
### XML representations
65 71

  
66 72
class DictTree:
67 73

  
......
169 175

  
170 176
iselement = ET.iselement
171 177

  
178

  
179
NSLIST = [BASE_NS_1_0, CISCO_BS_1_0]
180

  
172 181
def find(ele, tag, nslist=[]):
173 182
    """If *nslist* is empty, same as :meth:`xml.etree.ElementTree.Element.find`.
174 183
    If it is not, *tag* is interpreted as an unqualified name and qualified

Also available in: Unified diff