1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
|
"""
Handler for default device information.
Some devices require very specific information and action during client interaction.
The "device handlers" provide a number of callbacks that return the necessary
information. This allows the ncclient code to merely call upon this device handler -
once configured - instead of cluttering its code with if-statements.
Initially, not much is dealt with by the handler. However, in the future, as more
devices with specific handling are added, more handlers and more functions should be
implememted here, so that the ncclient code can use these callbacks to fill in the
device specific information.
Note that for proper import, the classname has to be:
"<Devicename>DeviceHandler"
...where <Devicename> is something like "Default", "Nexus", etc.
All device-specific handlers derive from the DefaultDeviceHandler, which implements the
generic information needed for interaction with a Netconf server.
"""
from ncclient.transport.parser import DefaultXMLParser
import sys
if sys.version >= '3':
xrange = range
class DefaultDeviceHandler(object):
"""
Default handler for device specific information.
"""
# Define the exempt error messages (those that shouldn't cause an exception).
# Wild cards are possible: Start and/or end with a '*' to indicate that the text
# can appear at the start, the end or the middle of the error message to still
# 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: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
# and/or end with a '*'.
self._exempt_errors_exact_match = []
self._exempt_errors_startwith_wildcard_match = []
self._exempt_errors_endwith_wildcard_match = []
self._exempt_errors_full_wildcard_match = []
for i in xrange(len(self._EXEMPT_ERRORS)):
e = self._EXEMPT_ERRORS[i].lower()
if e.startswith("*"):
if e.endswith("*"):
self._exempt_errors_full_wildcard_match.append(e[1:-1])
else:
self._exempt_errors_startwith_wildcard_match.append(e[1:])
elif e.endswith("*"):
self._exempt_errors_endwith_wildcard_match.append(e[:-1])
else:
self._exempt_errors_exact_match.append(e)
def add_additional_ssh_connect_params(self, kwargs):
"""
Add device specific parameters for the SSH connect.
Pass in the keyword-argument dictionary for the SSH connect call. The
dictionary will be modified (!) with the additional device-specific parameters.
"""
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):
"""
Return the capability list.
A list of URI's representing the client's capabilities. This is used during
the initial capability exchange. Modify (in a new device-handler subclass)
as needed.
"""
return self._BASE_CAPABILITIES + self.capabilities
def get_xml_base_namespace_dict(self):
"""
A dictionary containing the base namespace.
For lxml's nsmap, the base namespace should have a 'None' key.
{
None: "... base namespace... "
}
If no base namespace is needed, an empty dictionary should be
returned.
"""
return {}
def get_xml_extra_prefix_kwargs(self):
"""
Return any extra prefix that should be sent with each RPC request.
Since these are used as kwargs, the function should return
either an empty dictionary if there are no additional arguments,
or a dictionary with keyword parameters suitable fo the Element()
function. Mostly, this is the "nsmap" argument.
{
"nsmap" : {
... namespace definitions ...
}
}
"""
return {}
def get_ssh_subsystem_names(self):
"""
Return a list of names to try for the SSH subsystems.
This always returns a list, even if only a single subsystem name is used.
If the returned list contains multiple names then the various subsystems are
tried in order, until one of them can successfully connect.
"""
return [ "netconf" ]
def is_rpc_error_exempt(self, error_text):
"""
Check whether an RPC error message is excempt, thus NOT causing an exception.
On some devices the RPC operations may indicate an error response, even though
the operation actually succeeded. This may be in cases where a warning would be
more appropriate. In that case, the client may be better advised to simply
ignore that error and not raise an exception.
Note that there is also the "raise_mode", set on session and manager, which
controls the exception-raising behaviour in case of returned errors. This error
filter here is independent of that: No matter what the raise_mode says, if the
error message matches one of the exempt errors returned here, an exception
will not be raised.
The exempt error messages are defined in the _EXEMPT_ERRORS field of the device
handler object and can be overwritten by child classes. Wild cards are
possible: Start and/or end with a '*' to indicate that the text can appear at
the start, the end or the middle of the error message to still match. All
comparisons are case insensitive.
Return True/False depending on found match.
"""
if error_text is not None:
error_text = error_text.lower().strip()
else:
error_text = 'no error given'
# Compare the error text against all the exempt errors.
for ex in self._exempt_errors_exact_match:
if error_text == ex:
return True
for ex in self._exempt_errors_startwith_wildcard_match:
if error_text.endswith(ex):
return True
for ex in self._exempt_errors_endwith_wildcard_match:
if error_text.startswith(ex):
return True
for ex in self._exempt_errors_full_wildcard_match:
if ex in error_text:
return True
return False
def perform_qualify_check(self):
"""
During RPC operations, we perform some initial sanity checks on the responses.
This check will fail for some devices, in which case this function here should
return False in order to skip the test.
"""
return True
def add_additional_operations(self):
"""
Add device/vendor specific operations.
"""
return {}
def handle_raw_dispatch(self, raw):
return False
def handle_connection_exceptions(self, sshsession):
return False
def reply_parsing_error_transform(self, reply_cls):
"""
Hook for working around bugs in replies from devices (the root element can be "fixed")
:param reply_cls: the RPCReply class that is parsing the reply 'root' xml element
:return: transform function for the 'root' xml element of the RPC reply in case the normal parsing fails
"""
# No transformation by default
return None
def transform_reply(self):
return False
def transform_edit_config(self, node):
"""
Hook for working around bugs in devices that cannot deal with
standard config payloads for edits. This will be called
in EditConfig.request just before the request is submitted,
meaning it will get an XML tree rooted at edit-config.
:param node: the XML tree for edit-config
:return: either the original XML tree if no changes made or a modified XML tree
"""
return node
def get_xml_parser(self, session):
"""
vendor can chose which parser to use for RPC reply response.
Default being DOM
:param session: ssh session object
:return: default DOM parser
"""
return DefaultXMLParser(session)
|