Skip to content

Commit

Permalink
Merge 89adf69 into 96fd037
Browse files Browse the repository at this point in the history
  • Loading branch information
earies committed Apr 14, 2021
2 parents 96fd037 + 89adf69 commit 58171c6
Show file tree
Hide file tree
Showing 48 changed files with 103 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -25,3 +25,4 @@ ENV/
.coverage
docs/html
.tox
cover/
2 changes: 1 addition & 1 deletion examples/nc01.py → examples/base/nc01.py
Expand Up @@ -15,7 +15,7 @@
def demo(host, user):
with manager.connect(host=host, port=22, username=user) as m:
for c in m.server_capabilities:
print c
print(c)

if __name__ == '__main__':
demo(sys.argv[1], os.getenv("USER"))
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
34 changes: 34 additions & 0 deletions examples/base/nc09.py
@@ -0,0 +1,34 @@
#! /usr/bin/env python
#
# Connect to the NETCONF server passed on the command line while passing
# custom capabilities on the default device handler and display the
# client capabilities. For brevity and clarity of the examples, we omit
# proper exception handling.
#
# $ ./nc09.py host username password

import logging
import os
import sys
import warnings

warnings.simplefilter("ignore", DeprecationWarning)
from ncclient import manager

def demo(host, user, password):
with manager.connect(
host=host, port=830, username=user, password=password,
nc_params={
'capabilities': [
'urn:custom:capability:1.0',
'urn:custom:capability:2.0'
]},
hostkey_verify=False) as m:

for c in m.client_capabilities:
print(c)

if __name__ == '__main__':
LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s'
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT)
demo(sys.argv[1], sys.argv[2], sys.argv[3])
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
52 changes: 36 additions & 16 deletions ncclient/devices/default.py
Expand Up @@ -41,8 +41,26 @@ class DefaultDeviceHandler(object):
# match. All comparisons are case insensitive.
_EXEMPT_ERRORS = []

_BASE_CAPABILITIES = [
"urn:ietf:params:netconf:base:1.0",
"urn:ietf:params:netconf:base:1.1",
"urn:ietf:params:netconf:capability:writable-running:1.0",
"urn:ietf:params:netconf:capability:candidate:1.0",
"urn:ietf:params:netconf:capability:confirmed-commit:1.0",
"urn:ietf:params:netconf:capability:rollback-on-error:1.0",
"urn:ietf:params:netconf:capability:startup:1.0",
"urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp",
"urn:ietf:params:netconf:capability:validate:1.0",
"urn:ietf:params:netconf:capability:xpath:1.0",
"urn:ietf:params:netconf:capability:notification:1.0",
"urn:liberouter:params:netconf:capability:power-control:1.0",
"urn:ietf:params:netconf:capability:interleave:1.0",
"urn:ietf:params:netconf:capability:with-defaults:1.0"
]

def __init__(self, device_params=None):
self.device_params = device_params
self.capabilities = []
# Turn all exempt errors into lower case, since we don't want those comparisons
# to be case sensitive later on. Sort them into exact match, wildcard start,
# wildcard end, and full wildcard categories, depending on whether they start
Expand Down Expand Up @@ -74,6 +92,23 @@ def add_additional_ssh_connect_params(self, kwargs):
"""
pass

def add_additional_netconf_params(self, kwargs):
"""Add additional NETCONF parameters
Accept a keyword-argument dictionary to add additional NETCONF
parameters that may be in addition to those specified by the
default and device specific handlers.
Currently, only additional client specified capabilities are
supported and will be appended to default and device specific
capabilities.
Args:
kwargs: A dictionary of specific NETCONF parameters to
apply in addition to those derived by default and
device specific handlers.
"""
self.capabilities = kwargs.pop("capabilities", [])

def get_capabilities(self):
"""
Expand All @@ -84,22 +119,7 @@ def get_capabilities(self):
as needed.
"""
return [
"urn:ietf:params:netconf:base:1.0",
"urn:ietf:params:netconf:base:1.1",
"urn:ietf:params:netconf:capability:writable-running:1.0",
"urn:ietf:params:netconf:capability:candidate:1.0",
"urn:ietf:params:netconf:capability:confirmed-commit:1.0",
"urn:ietf:params:netconf:capability:rollback-on-error:1.0",
"urn:ietf:params:netconf:capability:startup:1.0",
"urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp",
"urn:ietf:params:netconf:capability:validate:1.0",
"urn:ietf:params:netconf:capability:xpath:1.0",
"urn:ietf:params:netconf:capability:notification:1.0",
"urn:liberouter:params:netconf:capability:power-control:1.0",
"urn:ietf:params:netconf:capability:interleave:1.0",
"urn:ietf:params:netconf:capability:with-defaults:1.0"
]
return self._BASE_CAPABILITIES + self.capabilities

def get_xml_base_namespace_dict(self):
"""
Expand Down
10 changes: 8 additions & 2 deletions ncclient/manager.py
Expand Up @@ -99,6 +99,10 @@ def _extract_manager_params(kwds):
manager_params['timeout'] = kwds['timeout']
return manager_params

def _extract_nc_params(kwds):
nc_params = kwds.pop("nc_params", {})

return nc_params

def connect_ssh(*args, **kwds):
"""
Expand All @@ -121,13 +125,15 @@ def connect_ssh(*args, **kwds):
A custom device handler can be provided with
`device_params={'handler':<handler class>}` in connection parameters.
"""
# Extract device parameter and manager parameter dictionaries, if they were passed into this function.
# Extract device/manager/netconf parameter dictionaries, if they were passed into this function.
# Remove them from kwds (which should keep only session.connect() parameters).
device_params = _extract_device_params(kwds)
manager_params = _extract_manager_params(kwds)
nc_params = _extract_nc_params(kwds)

device_handler = make_device_handler(device_params)
device_handler.add_additional_ssh_connect_params(kwds)
device_handler.add_additional_netconf_params(nc_params)
session = transport.SSHSession(device_handler)
if "hostkey_verify" not in kwds or kwds["hostkey_verify"]:
session.load_known_hosts()
Expand Down Expand Up @@ -175,7 +181,7 @@ def call_home(*args, **kwds):
srv_socket.bind((host, port))
srv_socket.settimeout(10)
srv_socket.listen()

sock, remote_host = srv_socket.accept()
logger.info('Callhome connection initiated from remote host {0}'.format(remote_host))
kwds['sock'] = sock
Expand Down
2 changes: 1 addition & 1 deletion ncclient/transport/session.py
Expand Up @@ -34,7 +34,7 @@ class NetconfBase(object):
BASE_10 = 1
BASE_11 = 2


class Session(Thread):

"Base class for use by transport protocol implementations."
Expand Down
28 changes: 22 additions & 6 deletions test/unit/test_manager.py
Expand Up @@ -124,9 +124,9 @@ def test_connect_outbound_ssh(self, mock_ssh):

@patch('ncclient.manager.connect_ioproc')
def test_connect_ioproc(self, mock_ssh):
manager.connect(host='localhost', device_params={'name': 'junos',
manager.connect(host='localhost', device_params={'name': 'junos',
'local': True})
mock_ssh.assert_called_once_with(host='localhost',
mock_ssh.assert_called_once_with(host='localhost',
device_params={'local': True, 'name': 'junos'})

@patch('paramiko.proxy.ProxyCommand')
Expand Down Expand Up @@ -175,7 +175,7 @@ def test_ioproc(self, mock_connect, mock_ioproc):
manager_params={'timeout': 10})
self.assertEqual(mock_connect.called, 1)
self.assertEqual(conn._timeout, 10)
self.assertEqual(conn._device_handler.device_params, {'local': True, 'name': 'junos'})
self.assertEqual(conn._device_handler.device_params, {'local': True, 'name': 'junos'})
self.assertEqual(
conn._device_handler.__class__.__name__,
"JunosDeviceHandler")
Expand Down Expand Up @@ -210,6 +210,21 @@ def test_manager_client_capability(
conn.client_capabilities,
conn._session.client_capabilities)

@patch('socket.socket')
@patch('paramiko.Transport')
@patch('ncclient.transport.ssh.hexlify')
@patch('ncclient.transport.ssh.Session._post_connect')
def test_manager_custom_client_capability(
self, mock_session, mock_hex, mock_trans, mock_socket):
custom_capabilities = [
'urn:custom:capability:1.0',
'urn:custom:capability:2.0'
]
conn = self._mock_manager(nc_params={'capabilities': custom_capabilities})
self.assertEqual(
conn.client_capabilities,
conn._session.client_capabilities)

@patch('socket.socket')
@patch('paramiko.Transport')
@patch('ncclient.transport.ssh.hexlify')
Expand Down Expand Up @@ -280,15 +295,16 @@ def test_manager_huge_node(self, mock_rpc, mock_session, default_value):
mock_rpc.assert_called_once()
self.assertFalse(mock_rpc.call_args[1]['huge_tree'])

def _mock_manager(self):
def _mock_manager(self, nc_params={}):
conn = manager.connect(host='10.10.10.10',
port=22,
username='user',
password='password',
timeout=3,
hostkey_verify=False, allow_agent=False,
device_params={'name': 'junos'},
manager_params={'timeout': 10})
manager_params={'timeout': 10},
nc_params=nc_params)
return conn

@patch('socket.fromfd')
Expand All @@ -308,7 +324,7 @@ def _mock_outbound_manager(self):
device_params={'name': 'junos'},
hostkey_verify=False, allow_agent=False)
return conn

@patch('socket.socket')
@patch('ncclient.manager.connect_ssh')
def test_call_home(self, mock_ssh, mock_socket_open):
Expand Down

0 comments on commit 58171c6

Please sign in to comment.