def _get_selection(elm, select): if select is None or len(select) == 0: return # Add non-default namespaces to filter element nsmap = {key: value for key, value in NSMAP.items() if key and key != "nc"} felm = util.subelm(elm, "nc:filter", nsmap=nsmap) if hasattr(select, "nsmap"): felm.attrib[qmap("nc") + "type"] = "subtree" felm.append(select) elif _is_filter(select): felm.attrib[qmap("nc") + "type"] = "subtree" felm.append(etree.fromstring(select)) else: felm.attrib[qmap("nc") + "type"] = "xpath" felm.attrib[qmap("nc") + "select"] = select
def _reader_handle_message(self, msg): """This function is called from the session reader thread to process a received framed netconf message. """ try: tree = etree.parse(io.BytesIO(msg.encode('utf-8'))) if not tree: raise SessionError(msg, "Invalid XML from server.") except etree.XMLSyntaxError: raise SessionError(msg, "Invalid XML from server.") replies = tree.xpath("/nc:rpc-reply", namespaces=NSMAP) if not replies: raise SessionError(msg, "No rpc-reply found") for reply in replies: try: msg_id = int(reply.get(qmap("nc") + 'message-id')) except (TypeError, ValueError): try: # Deal with servers not properly setting attribute namespace. msg_id = int(reply.get('message-id')) except (TypeError, ValueError): # # Cisco is returning errors without message-id attribute which # # is non-rfc-conforming it is doing this for any malformed XML # # not simply missing message-id attribute. # error = reply.xpath("nc:rpc-error", namespaces=self.nsmap) # if error: # raise RPCError(received, tree, error[0]) raise SessionError(msg, "No valid message-id attribute found") # Queue the message with self.cv: try: if msg_id not in self.rpc_out: if self.debug: logger.debug( "Ignoring unwanted reply for message-id %s", str(msg_id)) return elif self.rpc_out[msg_id] is not None: logger.warning( "Received multiple replies for message-id %s:" " before: %s now: %s", str(msg_id), str(self.rpc_out[msg_id]), str(msg)) if self.debug: logger.debug("%s: Received rpc-reply message-id: %s", str(self), str(msg_id)) self.rpc_out[msg_id] = tree, reply, msg except Exception as error: logger.debug("%s: Unexpected exception: %s", str(self), str(error)) raise finally: self.cv.notify_all()
def send_rpc_reply(self, rpc_reply, origmsg): reply = etree.Element(qmap("nc") + "rpc-reply", attrib=origmsg.attrib, nsmap=origmsg.nsmap) try: rpc_reply.getchildren # pylint: disable=W0104 reply.append(rpc_reply) except AttributeError: reply.extend(rpc_reply) ucode = etree.tounicode(reply, pretty_print=True) if self.debug: logger.debug("%s: Sending RPC-Reply: %s", str(self), str(ucode)) self.send_message(ucode)
def send_rpc_reply (self, rpc_reply, origmsg): reply = etree.Element(qmap('nc') + "rpc-reply", attrib=origmsg.attrib, nsmap=origmsg.nsmap) try: rpc_reply.getchildren # pylint: disable=W0104 reply.append(rpc_reply) except AttributeError: reply.extend(rpc_reply) ucode = etree.tounicode(reply, pretty_print=True) if self.debug: logger.debug("%s: Sending RPC-Reply: %s", str(self), str(ucode)) self.send_message(ucode)
def filter_results(rpc, data, filter_or_none, debug=False): """Check for a user filter and prune the result data accordingly. :param rpc: An RPC message element. :param data: The data to filter. :param filter_or_none: Filter element or None. :type filter_or_none: `lxml.Element` """ if filter_or_none is None: return data type_attr_name = qmap("nc") + "type" select_attr_name = qmap("nc") + "select" if type_attr_name not in filter_or_none.attrib or filter_or_none.attrib[ type_attr_name] == "subtree": # Check for the pathalogical case of empty filter since that's easy to implement. if not filter_or_none.getchildren(): return elm("nc:data") xpf = filter_to_xpath(filter_or_none) elif filter_or_none.attrib[type_attr_name] == "xpath": if select_attr_name not in filter_or_none.attrib: raise error.MissingAttributeProtoError(rpc, filter_or_none, select_attr_name) xpf = filter_or_none.attrib[select_attr_name] else: msg = "unexpected type: " + str(filter_or_none.attrib[type_attr_name]) raise error.BadAttributeProtoError(rpc, filter_or_none, type_attr_name, message=msg) logger.debug("Filtering on xpath expression: %s", str(xpf)) return xpath_filter_result(data, xpf)
def _send_rpc_reply(self, rpc_reply, origmsg): """Send an rpc-reply to the client. This is should normally not be called externally the return value from the rpc_* methods will be returned using this method. """ reply = etree.Element(qmap('nc') + "rpc-reply", attrib=origmsg.attrib, nsmap=origmsg.nsmap) try: rpc_reply.getchildren # pylint: disable=W0104 reply.append(rpc_reply) except AttributeError: reply.extend(rpc_reply) ucode = etree.tounicode(reply, pretty_print=True) if self.debug: logger.debug("%s: Sending RPC-Reply: %s", str(self), str(ucode)) self.send_message(ucode)
def __init__(self, origmsg, etype, tag, **kwargs): # Add attrib and nsmap from original message. self.reply = etree.Element(qmap("nc") + "rpc-reply", attrib=origmsg.attrib, nsmap=origmsg.nsmap) rpcerr = etree.SubElement(self.reply, qmap("nc") + "rpc-error") # We require a type, tag, and severity assuming error for severity. if etype in RPCERR_TYPE_ENUM: etype = RPCERR_TYPE_ENUM[etype] etree.SubElement(rpcerr, qmap("nc") + "error-type").text = str(etype) etree.SubElement(rpcerr, qmap("nc") + "error-tag").text = tag if "severity" not in kwargs: etree.SubElement(rpcerr, qmap("nc") + "error-severity").text = "error" # Now convert any other arguments to xml for key, value in kwargs.items(): # Allow info to be a dictionary we convert to sub-elements if key == "info" and hasattr(value, "items"): infoelm = etree.SubElement(rpcerr, qmap("nc") + "error-info") for ikey, ivalue in value.items(): ikey = ikey.replace('_', '-') etree.SubElement(infoelm, "{}".format(ikey)).text = str(ivalue) else: key = key.replace('_', '-') etree.SubElement(rpcerr, qmap("nc") + "error-{}".format(key)).text = str(value) # This sort of sucks for humans super(RPCServerError, self).__init__(self.get_reply_msg())
def reader_handle_message(self, msg): """Handle a message, lock is already held""" if not self.session_open: return # Any error with XML encoding here is going to cause a session close # Technically we should be able to return malformed message I think. try: tree = etree.parse(io.BytesIO(msg.encode("utf-8"))) if not tree: raise ncerror.SessionError(msg, "Invalid XML from client.") except etree.XMLSyntaxError: logger.warning("Closing session due to malformed message") raise ncerror.SessionError(msg, "Invalid XML from client.") rpcs = tree.xpath("/nc:rpc", namespaces=NSMAP) if not rpcs: raise ncerror.SessionError(msg, "No rpc found") for rpc in rpcs: try: msg_id = rpc.get("message-id") if self.debug: logger.debug("%s: Received rpc message-id: %s", str(self), msg_id) except (TypeError, ValueError): raise ncerror.SessionError(msg, "No valid message-id attribute found") try: # Get the first child of rpc as the method name rpc_method = rpc.getchildren() if len(rpc_method) != 1: if self.debug: logger.debug("%s: Bad Msg: msg-id: %s", str(self), msg_id) raise ncerror.RPCSvrErrBadMsg(rpc) rpc_method = rpc_method[0] rpcname = rpc_method.tag.replace(qmap("nc"), "") params = rpc_method.getchildren() paramslen = len(params) if rpcname == "close-session": # XXX should be RPC-unlocking if need be if self.debug: logger.debug("%s: Received close-session msg-id: %s", str(self), msg_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "kill-session": # XXX we are supposed to cleanly abort anything underway if self.debug: logger.debug("%s: Received kill-session msg-id: %s", str(self), msg_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "get": # Validate GET parameters if paramslen > 1: # XXX need to specify all elements not known raise ncerror.RPCSvrErrBadMsg(rpc) if params and not util.filter_tag_match(params[0], "nc:filter"): raise ncerror.RPCSvrUnknownElement(rpc, params[0]) if not params: params = [None] elif rpcname == "get-config": # Validate GET-CONFIG parameters # XXX verify that the source parameter is present if paramslen > 2: # XXX need to specify all elements not known raise ncerror.RPCSvrErrBadMsg(rpc) source_param = rpc_method.find("nc:source", namespaces=NSMAP) if source_param is None: raise ncerror.RPCSvrMissingElement(rpc, util.elm("nc:source")) filter_param = None if paramslen == 2: filter_param = rpc_method.find("nc:filter", namespaces=NSMAP) if filter_param is None: unknown_elm = params[0] if params[0] != source_param else params[1] raise ncerror.RPCSvrUnknownElement(rpc, unknown_elm) params = [source_param, filter_param] # ------------------ # Call the method. # ------------------ try: # Handle any namespaces or prefixes in the tag, other than # "nc" which was removed above. Of course, this does not handle # namespace collisions, but that seems reasonable for now. rpcname = rpcname.rpartition("}")[-1] method_name = "rpc_" + rpcname.replace("-", "_") method = getattr(self.methods, method_name, self._rpc_not_implemented) # logger.debug("%s: Calling method: %s", str(self), str(methodname)) reply = method(self, rpc, *params) self.send_rpc_reply(reply, rpc) except NotImplementedError: raise ncerror.RPCSvrErrNotImpl(rpc) except ncerror.RPCSvrErrBadMsg as msgerr: if self.new_framing: self.send_message(msgerr.get_reply_msg()) else: # If we are 1.0 we have to simply close the connection # as we are not allowed to send this error logger.warning("Closing 1.0 session due to malformed message") raise ncerror.SessionError(msg, "Malformed message") except ncerror.RPCServerError as error: self.send_message(error.get_reply_msg()) except EOFError: if self.debug: logger.debug("Got EOF in reader_handle_message") except Exception as exception: error = ncerror.RPCSvrException(rpc, exception) self.send_message(error.get_reply_msg())
def _reader_handle_message(self, msg): if not self.session_open: return msg = msg.replace('\n', '').replace('\r', '').replace('\t', '') # Any error with XML encoding here is going to cause a session close # Technically we should be able to return malformed message I think. try: tree = etree.parse(io.BytesIO(msg.encode('utf-8'))) if not tree: raise ncerror.SessionError(msg, "Invalid XML from client.") except etree.XMLSyntaxError: logger.warning("Closing session due to malformed message") raise ncerror.SessionError(msg, "Invalid XML from client.") if self.debug and msg.find('message-id="-1"') == -1: logger.debug("%s: Recieved RPC message:\n%s", str(self), str(etree.tounicode(tree, pretty_print=True))) rpcs = tree.xpath("/nc:rpc", namespaces=NSMAP) if not rpcs: raise ncerror.SessionError(msg, "No rpc found") for rpc in rpcs: try: msg_id = rpc.get('message-id') if self.debug: logger.debug("%s: Received rpc message-id: %s", str(self), msg_id) except (TypeError, ValueError): raise ncerror.SessionError( msg, "No valid message-id attribute found") try: # Get the first child of rpc as the method name rpc_method = rpc.getchildren() if len(rpc_method) != 1: if self.debug: logger.debug("%s: Bad Msg: msg-id: %s", str(self), msg_id) raise ncerror.MalformedMessageRPCError(rpc) rpc_method = rpc_method[0] rpcname = rpc_method.tag.replace(qmap('nc'), "") params = rpc_method.getchildren() paramslen = len(params) lock_target = None if self.debug: logger.debug("%s: RPC: %s: paramslen: %s", str(self), rpcname, str(paramslen)) if rpcname == "close-session": # XXX should be RPC-unlocking if need be if self.debug: logger.debug("%s: Received close-session msg-id: %s", str(self), msg_id) self._send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "kill-session": # XXX we are supposed to cleanly abort anything underway if self.debug: logger.debug("%s: Received kill-session msg-id: %s", str(self), msg_id) self._send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "get": # Validate GET parameters if paramslen > 1: # XXX need to specify all elements not known raise ncerror.MalformedMessageRPCError(rpc) if params and not util.filter_tag_match( params[0], "nc:filter"): raise ncerror.UnknownElementProtoError(rpc, params[0]) if not params: params = [None] elif rpcname == "get-config": # Validate GET-CONFIG parameters if paramslen > 2: # XXX Should be ncerror.UnknownElementProtoError? for each? raise ncerror.MalformedMessageRPCError(rpc) source_param = rpc_method.find("nc:source", namespaces=NSMAP) if source_param is None: raise ncerror.MissingElementProtoError( rpc, util.qname("nc:source")) filter_param = None if paramslen == 2: filter_param = rpc_method.find("nc:filter", namespaces=NSMAP) if filter_param is None: unknown_elm = params[ 0] if params[0] != source_param else params[1] raise ncerror.UnknownElementProtoError( rpc, unknown_elm) params = [source_param, filter_param] elif rpcname == "lock" or rpcname == "unlock": if paramslen != 1: raise ncerror.MalformedMessageRPCError(rpc) target_param = rpc_method.find("nc:target", namespaces=NSMAP) if target_param is None: raise ncerror.MissingElementProtoError( rpc, util.qname("nc:target")) elms = target_param.getchildren() if len(elms) != 1: raise ncerror.MissingElementProtoError( rpc, util.qname("nc:target")) lock_target = elms[0].tag.replace(qmap('nc'), "") if lock_target not in ["running", "candidate"]: raise ncerror.BadElementProtoError( rpc, util.qname("nc:target")) params = [lock_target] if rpcname == "lock": logger.error("%s: Lock Target: %s", str(self), lock_target) # Try and obtain the lock. locksid = self.server.lock_target(self, lock_target) if locksid: raise ncerror.LockDeniedProtoError(rpc, locksid) elif rpcname == "unlock": logger.error("%s: Unlock Target: %s", str(self), lock_target) # Make sure we have the lock. locksid = self.server.is_target_locked(lock_target) if locksid != self.session_id: # An odd error to return raise ncerror.LockDeniedProtoError(rpc, locksid) #------------------ # Call the method. #------------------ try: # Handle any namespaces or prefixes in the tag, other than # "nc" which was removed above. Of course, this does not handle # namespace collisions, but that seems reasonable for now. rpcname = rpcname.rpartition("}")[-1] method_name = "rpc_" + rpcname.replace('-', '_') method = getattr(self.methods, method_name, None) if method is None: if rpcname in self.handled_rpc_methods: self._send_rpc_reply(etree.Element("ok"), rpc) method = None else: method = self._rpc_not_implemented if method is not None: if self.debug: logger.debug("%s: Calling method: %s", str(self), method_name) reply = method(self, rpc, *params) self._send_rpc_reply(reply, rpc) except Exception: # If user raised error unlock if this was lock if rpcname == "lock" and lock_target: self.server.unlock_target(self, lock_target) raise # If this was unlock and we're OK, release the lock. if rpcname == "unlock": self.server.unlock_target(self, lock_target) except ncerror.MalformedMessageRPCError as msgerr: if self.new_framing: if self.debug: logger.debug("%s: MalformedMessageRPCError: %s", str(self), str(msgerr)) self.send_message(msgerr.get_reply_msg()) else: # If we are 1.0 we have to simply close the connection # as we are not allowed to send this error logger.warning( "Closing 1.0 session due to malformed message") raise ncerror.SessionError(msg, "Malformed message") except ncerror.RPCServerError as error: if self.debug: logger.debug("%s: RPCServerError: %s", str(self), str(error)) self._send_rpc_reply_error(error) except EOFError: if self.debug: logger.debug("%s: Got EOF in reader_handle_message", str(self)) error = ncerror.RPCSvrException(rpc, EOFError("EOF")) self._send_rpc_reply_error(error) except Exception as exception: if self.debug: logger.debug( "%s: Got unexpected exception in reader_handle_message: %s", str(self), str(exception)) error = ncerror.RPCSvrException(rpc, exception) self._send_rpc_reply_error(error)
class MockMethods(object): NCFILTER = qmap("nc") + "filter" """This is an abstract class that is used to document the server methods functionality The server return not-implemented if the method is not found in the methods object, so feel free to use duck-typing here (i.e., no need to inherit) """ def nc_append_capabilities(self, capabilities): # pylint: disable=W0613 """The server should append any capabilities it supports to capabilities""" ncutil.subelm(capabilities, "capability").text = mock_module ncutil.subelm(capabilities, "capability").text = "urn:ietf:params:netconf:capability:xpath:1.0" def rpc_get(self, session, rpc, filter_or_none): # pylint: disable=W0613 data = ncutil.elm("data") cont = ncutil.subelm(data, "interfaces") listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "Ethernet0/0")) listval.append(ncutil.leaf_elm("shutdown", "true")) listval.append(ncutil.leaf_elm("state", "down")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "Ethernet0/1")) listval.append(ncutil.leaf_elm("shutdown", "false")) listval.append(ncutil.leaf_elm("state", "down")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "FastEthernet1/0")) listval.append(ncutil.leaf_elm("shutdown", "false")) listval.append(ncutil.leaf_elm("state", "up")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "FastEthernet1/1")) listval.append(ncutil.leaf_elm("shutdown", "false")) listval.append(ncutil.leaf_elm("state", "down")) return ncutil.filter_results(rpc, data, filter_or_none) def rpc_get_config(self, session, rpc, source_elm, filter_or_none): # pylint: disable=W0613 assert source_elm is not None if source_elm.find("nc:running", namespaces=NSMAP) is None: # Really this should be a different error its a bad value for source not missing raise ncerror.MissingElementProtoError(rpc, ncutil.qname("nc:running")) data = ncutil.elm("data") cont = ncutil.subelm(data, "interfaces") listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "Ethernet0/0")) listval.append(ncutil.leaf_elm("shutdown", "true")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "Ethernet0/1")) listval.append(ncutil.leaf_elm("shutdown", "false")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "FastEthernet1/0")) listval.append(ncutil.leaf_elm("shutdown", "false")) listval = ncutil.subelm(cont, "interface") listval.append(ncutil.leaf_elm("name", "FastEthernet1/1")) listval.append(ncutil.leaf_elm("shutdown", "false")) # Missing from operational listval.append(ncutil.leaf_elm("name", "GigabitEthernet2/0")) listval.append(ncutil.leaf_elm("shutdown", "false")) return ncutil.filter_results(rpc, data, filter_or_none) #--------------------------------------------------------------------------- # These definitions will change to include required parameters like get and # get-config #--------------------------------------------------------------------------- # XXX The API WILL CHANGE consider unfinished def rpc_copy_config(self, unused_session, rpc, *unused_params): raise ncerror.OperationNotSupportedProtoError(rpc) # XXX The API WILL CHANGE consider unfinished def rpc_delete_config(self, unused_session, rpc, *unused_params): raise ncerror.OperationNotSupportedProtoError(rpc) # XXX The API WILL CHANGE consider unfinished def rpc_edit_config(self, unused_session, rpc, *unused_params): raise ncerror.OperationNotSupportedProtoError(rpc) # XXX The API WILL CHANGE consider unfinished def rpc_lock(self, unused_session, rpc, *unused_params): raise ncerror.OperationNotSupportedProtoError(rpc) # XXX The API WILL CHANGE consider unfinished def rpc_unlock(self, unused_session, rpc, *unused_params): raise ncerror.OperationNotSupportedProtoError(rpc)
def _handle_message(self, msg): ''' 'Handle a message, lock is already held ''' if not self.session_open: return try: tree = etree.parse(io.BytesIO(msg.encode('utf-8'))) if not tree: raise ncerror.SessionError(msg, "Invalid XML from client.") except etree.XMLSyntaxError: GlobalModule.EM_LOGGER.warning( "202010 Closing session due to malformed message") raise ncerror.SessionError(msg, "Invalid XML from client.") rpcs = tree.xpath("/nc:rpc", namespaces=NSMAP) if not rpcs: raise ncerror.SessionError(msg, "No rpc found") for rpc in rpcs: try: msg_id = rpc.get('message-id') if self.debug: GlobalModule.EM_LOGGER.debug( "%s: Received rpc message-id: %s", str(self), msg_id) except (TypeError, ValueError): raise ncerror.SessionError( msg, "No valid message-id attribute found") try: rpc_method = rpc.getchildren() if len(rpc_method) != 1: if self.debug: GlobalModule.EM_LOGGER.debug("%s: Bad Msg: msg-id: %s", str(self), msg_id) raise ncerror.RPCSvrErrBadMsg(rpc) rpc_method = rpc_method[0] rpcname = rpc_method.tag.replace(qmap('nc'), "") params = rpc_method.getchildren() paramslen = len(params) if rpcname == "close-session": if self.debug: GlobalModule.EM_LOGGER.debug( "%s: Received close-session msg-id: %s", str(self), msg_id) GlobalModule.EM_LOGGER.info("102007 Connection Closed: %s", str(self)) EmNetconfSessionDate.del_session_date(self.session_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() return elif rpcname == "kill-session": if self.debug: GlobalModule.EM_LOGGER.debug( "%s: Received kill-session msg-id: %s", str(self), msg_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() return elif rpcname == "get": if paramslen > 1: raise ncerror.RPCSvrErrBadMsg(rpc) if params and not util.filter_tag_match( params[0], "nc:filter"): raise ncerror.RPCSvrUnknownElement(rpc, params[0]) if not params: params = [None] elif rpcname == "get-config": GlobalModule.EM_LOGGER.info("102004 Receiving get-config") if paramslen > 2: raise ncerror.RPCSvrErrBadMsg(rpc) source_param = rpc_method.find("nc:source", namespaces=NSMAP) if source_param is None: raise ncerror.RPCSvrMissingElement( rpc, util.elm("nc:source")) filter_param = None if paramslen == 2: filter_param = rpc_method.find("nc:filter", namespaces=NSMAP) if filter_param is None: unknown_elm = params[ 0] if params[0] != source_param else params[1] raise ncerror.RPCSvrUnknownElement( rpc, unknown_elm) params = [source_param, filter_param] elif rpcname == "edit-config": GlobalModule.EM_LOGGER.info("102008 Receiving edit-config") if paramslen > 2: raise ncerror.RPCSvrErrBadMsg(rpc) target_param = rpc_method.find("nc:target", namespaces=NSMAP) if target_param is None: raise ncerror.RPCSvrMissingElement( rpc, util.elm("nc:target")) config_param = None if paramslen == 2: config_param = rpc_method.find("nc:config", namespaces=NSMAP) if config_param is None: unknown_elm = params[ 0] if params[0] != config_param else params[1] raise ncerror.RPCSvrUnknownElement( rpc, unknown_elm) params = [target_param, config_param] try: rpcname = rpcname.rpartition("}")[-1] method_name = "rpc_" + rpcname.replace('-', '_') method = getattr(self.methods, method_name, self._rpc_not_implemented) result, reply = method(self.session_id, rpc, *params) if result is not True: self.send_rpc_reply(reply, rpc) except NotImplementedError: raise ncerror.RPCSvrErrNotImpl(rpc) except ncerror.RPCSvrErrBadMsg as msgerr: if self.new_framing: self.send_message(msgerr.get_reply_msg()) else: GlobalModule.EM_LOGGER.warning( "Closing 1.0 session due to malformed message") raise ncerror.SessionError(msg, "Malformed message") except ncerror.RPCServerError as error: self.send_message(error.get_reply_msg()) except Exception as exception: GlobalModule.EM_LOGGER.debug(traceback.format_exc()) error = ncerror.RPCSvrException(rpc, exception) self.send_message(error.get_reply_msg())
class NetconfServer(object): NCFILTER = qmap("nc") + "filter" def __init__(self, device, host_key, ssh_port=830, username=None, password=None, debug=False): #----------------- # Open the device #----------------- self.debug = debug host_key_path = os.path.expanduser(host_key) assert os.path.exists(host_key_path) idn = device.get_idn_data() # XXX Is this where we get the port count? assert idn[4] == "cal04" or idn[4] == "cal02" if idn[4] == "cal04": self.nports = 4 self.is_tfm = False else: self.nports = 1 self.is_tfm = True self.device = device self.device_lock = threading.Lock() #----------------------- # Start the server. #----------------------- self.controller = server.SSHUserPassController(username=username, password=password) self.server = server.NetconfSSHServer(server_ctl=self.controller, port=ssh_port, host_key=host_key_path, server_methods=self, debug=debug) logger.info("Listening on port %d", self.server.port) def join(self): "Wait on server to terminate" self.server.join() def nc_append_capabilities(self, caps): ncutil.subelm(caps, "capability").text = NSMAP['j'] def _run_device_method(self, rpc, method, *args, **kwargs): try: with self.device_lock: return method(*args, **kwargs) except jerror.OCMError as err: raise ncerror.RPCServerError(rpc, ncerror.RPCERR_TYPE_APPLICATION, ncerror.RPCERR_TAG_OPERATION_FAILED, message=str(err)) except Exception as ex: raise ncerror.RPCServerError(rpc, ncerror.RPCERR_TYPE_APPLICATION, ncerror.RPCERR_TAG_OPERATION_FAILED, app_tag="unexpected-error", message=str(ex)) def _rpc_param_get_frequency(self, rpc, params): for param in params: if ncutil.filter_tag_match("j:frequency", param.tag): break else: raise ncerror.RPCSvrMissingElement(rpc, ncutil.elm("j:frequency")) freq = param.strip() try: freq = int(freq.text) if not (190000 <= freq <= 198000): raise ncerror.RPCSvrBadElement( rpc, freq, message="Frequency not in range [190000, 198000]") except ValueError: raise ncerror.RPCSvrBadElement(rpc, freq, message="Frequency not an integer") def _rpc_param_get_boolean(self, rpc, tag, default, params): for param in params: if ncutil.filter_tag_match(tag, param.tag): if param is None: raise ncerror.RPCSvrBadElement( rpc, param, message="invalid boolean value for " + tag) bval = param.text.strip().lower() if bval in ["false", "no", "0"]: return False elif bval in ["true", "yes", "1"]: return True raise ncerror.RPCSvrBadElement( rpc, param, message="invalid boolean value for " + tag) if default is None: raise ncerror.RPCSvrMissingElement(rpc, ncutil.elm(tag)) return default def rpc_activate(self, unused, rpc, *params): # Input values if params: raise ncerror.RPCSvrErrBadMsg(rpc) self._run_device_method(rpc, self.device.activate) return ncutil.elm("ok") def rpc_self_test(self, unused, rpc, *params): # Input values if params: raise ncerror.RPCSvrErrBadMsg(rpc) self._run_device_method(rpc, self.device.self_test) return ncutil.elm("ok") def rpc_reset(self, unused, rpc, *params): # Input values if params: raise ncerror.RPCSvrErrBadMsg(rpc) self._run_device_method(rpc, self.device.reset) return ncutil.elm("ok") def rpc_frequency_power(self, unused, rpc, *params): if len(params) > 1: # XXX need a function to look for unknown elements and raise exc for those. # XXX really need a better error for not handled params raise ncerror.RPCSvrInvalidValue(rpc, message="Too many parameter") freq = self._rpc_param_get_frequency(rpc, params) power = self._run_device_method(rpc, self.device.get_freq_power, freq) result = ncutil.elm("data") result.append(ncutil.leaf_elm("j:power", "{:.2f}".format(power.dBm))) return result def rpc_full_itu_scan(self, unused_session, rpc, *params): # No input values yet try: if len(params) > 2: raise ncerror.RPCSvrInvalidValue(rpc, message="Too many parameters") # XXX Should be able to use "j:high-resolution" but it fails hires = self._rpc_param_get_boolean(rpc, "high-resolution", False, params) power_only = not self._rpc_param_get_boolean( rpc, "detect-presence", False, params) if power_only: points = self._run_device_method( rpc, self.device.get_itu_power_scan, hires) else: points = self._run_device_method(rpc, self.device.get_itu_scan, hires) result = ncutil.elm("data") for tup in points: ptelm = ncutil.subelm(result, "j:point") ptelm.append(ncutil.leaf_elm("j:frequency", tup[0])) if power_only: ptelm.append( ncutil.leaf_elm("j:power", "{:.2f}".format(tup[1].dBm))) else: ptelm.append( ncutil.leaf_elm("j:power", "{:.2f}".format(tup[2].dBm))) ptelm.append(ncutil.leaf_elm("j:channel-presence", tup[1])) return result except jerror.OCMError as ocmerr: logger.error("Got OCM error in full itu scan: %s: %s", str(ocmerr), traceback.format_exc()) except Exception as err: logger.error("Got error in full itu scan: %s: %s", str(err), traceback.format_exc()) raise def _rpc_full_scan(self, method, rpc, *params): # No input values yet if params: logging.error("%s: _rpc_full_scan got unexpected params", str(self)) raise ncerror.RPCSvrErrBadMsg(rpc) rv = self._run_device_method(rpc, method) result = ncutil.elm("data") for port, points in rv: portelm = ncutil.elm("j:port") result.append(portelm) portelm.append(ncutil.leaf_elm("j:port-index", port)) for freq, power in points: ptelm = ncutil.subelm(portelm, "j:point") ptelm.append(ncutil.leaf_elm("j:frequency", freq)) ptelm.append( ncutil.leaf_elm("j:power", "{:.2f}".format(power.dBm))) return result def rpc_full_scan(self, unused_session, rpc, *params): return self._rpc_full_scan(self.device.get_full_scan, rpc, *params) # No input values yet if params: raise ncerror.RPCSvrErrBadMsg(rpc) def rpc_full_125_scan(self, unused_session, rpc, *params): return self._rpc_full_scan(self.device.get_full_125_scan, rpc, *params) def rpc_get_config(self, unused_session, rpc, source_elm, unused_filter_elm): assert source_elm is not None if source_elm.find("nc:running", namespaces=NSMAP) is None: raise ncerror.RPCSvrMissingElement(rpc, ncutil.elm("nc:running")) config = ncutil.elm("data") if self.is_tfm: profile_elm = ncutil.elm("j:scan-profile") config.append(profile_elm) profile_elm.append( ncutil.leaf_elm("j:channel-spacing", self.device.get_channel_spacing())) profile_elm.append( ncutil.leaf_elm("j:frequency-start", self.device.get_start_freq())) profile_elm.append( ncutil.leaf_elm("j:frequency-end", self.device.get_stop_freq())) else: for idx in range(1, 17): profile_elm = ncutil.elm("j:channel-profile") config.append(profile_elm) profile_elm.append(ncutil.leaf_elm("j:profile-index", idx)) channels = self.device.get_channel_profile(idx) for freqs, freqe in channels: channel_elm = ncutil.subelm(profile_elm, "j:channel") range_elm = ncutil.subelm(channel_elm, "j:range") range_elm.append( ncutil.leaf_elm("j:frequency-start", freqs)) range_elm.append(ncutil.leaf_elm("j:frequency-end", freqe)) return config def rpc_get(self, unused_session, unused_rpc, filter_elm): data = ncutil.elm("data") get_data_methods = { ncutil.qname("j:ocm-type").text: self.device.get_device_type, ncutil.qname("j:oper-mode").text: self.device.get_oper_mode, ncutil.qname("j:ident-data").text: self.device.get_idn_string, ncutil.qname("j:device-info").text: self.device.get_module_info, ncutil.qname("j:application-version").text: self.device.get_app_version, ncutil.qname("j:temp").text: self.device.get_temp_int, } if not self.device.get_device_type().startswith("tf"): get_data_methods[ncutil.qname( "j:safe-version").text] = self.device.get_safe_version infonode = ncutil.elm("j:info") # infonode = etree.Element(ncutil.qname("j:info"), nsmap={ 'j': NSMAP['j'] }) # Get filter children children = [] if filter_elm is not None: children = filter_elm.getchildren( ) if filter_elm is not None else [] def get_all_values(): leaf_elms = [] for key, valuef in get_data_methods.items(): leaf_elms.append(ncutil.leaf_elm(key, valuef())) return leaf_elms # No filter children return info only if not children: ncutil.filter_leaf_values(None, infonode, get_all_values(), data) return data # Look for info filter. finfo = filter_elm.find("info") or filter_elm.find("j:info", namespaces=NSMAP) if finfo is not None: children = finfo.getchildren() if not children: leaf_elms = get_all_values() else: leaf_elms = [] for felm in children: tag = felm.tag if tag in get_data_methods: leaf_elms.append( ncutil.leaf_elm(tag, get_data_methods[tag]())) rv = ncutil.filter_leaf_values(finfo, infonode, leaf_elms, data) # Some selector doesn't match return empty. if rv is False: logger.error("XXX returning False") return data return data
def _rpc_not_implemented(self, unused_session, rpc, *unused_params): if self.debug: msg_id = rpc.get(qmap("nc") + 'message-id') logger.debug("%s: Not Impl msg-id: %s", str(self), msg_id) raise ncerror.OperationNotSupportedProtoError(rpc)
def reader_handle_message(self, msg): """Handle a message, lock is already held""" print('stpe 1') print(msg) if not self.session_open: return # Any error with XML encoding here is going to cause a session close # Technically we should be able to return malformed message I think. try: tree = etree.parse(io.BytesIO(msg.encode('utf-8'))) print('step 1.5') print(etree.tostring(tree, pretty_print=True)) if not tree: raise ncerror.SessionError(msg, "Invalid XML from client.") except etree.XMLSyntaxError: logger.warning("Closing session due to malformed message") raise ncerror.SessionError(msg, "Invalid XML from client.") rpcs = tree.xpath("/nc:rpc", namespaces=NSMAP) print('step 2') print(rpcs) if not rpcs: raise ncerror.SessionError(msg, "No rpc found") for rpc in rpcs: try: msg_id = rpc.get('message-id') print('step 3') print(msg_id) if self.debug: logger.debug("%s: Received rpc message-id: %s", str(self), msg_id) except (TypeError, ValueError): raise ncerror.SessionError( msg, "No valid message-id attribute found") try: # Get the first child of rpc as the method name rpc_method = rpc.getchildren() print('step 4') print(rpc_method) if len(rpc_method) != 1: if self.debug: logger.debug("%s: Bad Msg: msg-id: %s", str(self), msg_id) raise ncerror.RPCSvrErrBadMsg(rpc) rpc_method = rpc_method[0] print('step 5') print(rpc_method) rpcname = rpc_method.tag.replace(qmap('nc'), "") print('stpe 6') print(rpcname) params = rpc_method.getchildren() print('stpe 7') print(params) paramslen = len(params) if self.debug: logger.debug("%s: RPC: %s: paramslen: %s", str(self), rpcname, str(paramslen)) if rpcname == "close-session": # XXX should be RPC-unlocking if need be if self.debug: logger.debug("%s: Received close-session msg-id: %s", str(self), msg_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "kill-session": # XXX we are supposed to cleanly abort anything underway if self.debug: logger.debug("%s: Received kill-session msg-id: %s", str(self), msg_id) self.send_rpc_reply(etree.Element("ok"), rpc) self.close() # XXX should we also call the user method if it exists? return elif rpcname == "get": # Validate GET parameters if paramslen > 1: # XXX need to specify all elements not known raise ncerror.RPCSvrErrBadMsg(rpc) if params and not util.filter_tag_match( params[0], "nc:filter"): raise ncerror.RPCSvrUnknownElement(rpc, params[0]) if not params: params = [None] elif rpcname == "get-config": # Validate GET-CONFIG parameters # XXX verify that the source parameter is present if paramslen > 2: # XXX need to specify all elements not known raise ncerror.RPCSvrErrBadMsg(rpc) source_param = rpc_method.find("nc:source", namespaces=NSMAP) if source_param is None: raise ncerror.RPCSvrMissingElement( rpc, util.elm("nc:source")) filter_param = None if paramslen == 2: filter_param = rpc_method.find("nc:filter", namespaces=NSMAP) if filter_param is None: unknown_elm = params[ 0] if params[0] != source_param else params[1] raise ncerror.RPCSvrUnknownElement( rpc, unknown_elm) params = [source_param, filter_param] #------------------ # Call the method. #------------------ try: # Handle any namespaces or prefixes in the tag, other than # "nc" which was removed above. Of course, this does not handle # namespace collisions, but that seems reasonable for now. rpcname = rpcname.rpartition("}")[-1] print('step 8') print(rpcname) method_name = "rpc_" + rpcname.replace('-', '_') method = getattr(self.methods, method_name, self._rpc_not_implemented) if self.debug: logger.debug("%s: Calling method: %s", str(self), method_name) reply = method(self, rpc, *params) print('stpe 9') print(reply) self.send_rpc_reply(reply, rpc) except NotImplementedError: raise ncerror.RPCSvrErrNotImpl(rpc) except ncerror.RPCSvrErrBadMsg as msgerr: if self.new_framing: if self.debug: logger.debug("%s: RPCSvrErrBadMsg: %s", str(self), str(msgerr)) self.send_message(msgerr.get_reply_msg()) else: # If we are 1.0 we have to simply close the connection # as we are not allowed to send this error logger.warning( "Closing 1.0 session due to malformed message") raise ncerror.SessionError(msg, "Malformed message") except ncerror.RPCServerError as error: if self.debug: logger.debug("%s: RPCServerError: %s", str(self), str(error)) self.send_message(error.get_reply_msg()) except EOFError: if self.debug: logger.debug("%s: Got EOF in reader_handle_message", str(self)) error = ncerror.RPCSvrException(rpc, EOFError("EOF")) self.send_message(error.get_reply_msg()) except EOFError: if self.debug: logger.debug("Got EOF in reader_handle_message") except Exception as exception: if self.debug: logger.debug( "%s: Got unexpected exception in reader_handle_message: %s", str(self), str(exception)) error = ncerror.RPCSvrException(rpc, exception) self.send_message(error.get_reply_msg())
def _reader_handle_message(self, msg): """This function is called from the session reader thread to process a received framed netconf message. """ try: tree = etree.parse(io.BytesIO(msg.encode('utf-8'))) if not tree: raise SessionError(msg, "Invalid XML from server.") except etree.XMLSyntaxError: raise SessionError(msg, "Invalid XML from server.") replies = tree.xpath("/nc:rpc-reply", namespaces=NSMAP) tmp_NS = {'tmp': 'urn:ietf:params:xml:ns:netconf:notification:1.0'} notifications = tree.xpath("/tmp:notification", namespaces=tmp_NS) if not replies and not notifications: raise SessionError(msg, "No rpc-reply found") for reply in replies: try: msg_id = int(reply.get(qmap("nc") + 'message-id')) except (TypeError, ValueError): try: # Deal with servers not properly setting attribute namespace. msg_id = int(reply.get('message-id')) except (TypeError, ValueError): # # Cisco is returning errors without message-id attribute which # # is non-rfc-conforming it is doing this for any malformed XML # # not simply missing message-id attribute. # error = reply.xpath("nc:rpc-error", namespaces=self.nsmap) # if error: # raise RPCError(received, tree, error[0]) raise SessionError(msg, "No valid message-id attribute found") # Queue the message with self.cv: try: if msg_id not in self.rpc_out: if self.debug: logger.debug( "Ignoring unwanted reply for message-id %s", str(msg_id)) return elif self.rpc_out[msg_id] is not None: logger.warning( "Received multiple replies for message-id %s:" " before: %s now: %s", str(msg_id), str(self.rpc_out[msg_id]), str(msg)) if self.debug: logger.debug("%s: Received rpc-reply message-id: %s", str(self), str(msg_id)) self.rpc_out[msg_id] = tree, reply, msg except Exception as error: logger.debug("%s: Unexpected exception: %s", str(self), str(error)) raise finally: self.cv.notify_all() for notif in notifications: push_update_node = notif.find("yp:push-update", namespaces=NSMAP) id_node = push_update_node.find("tmp:id", namespaces=tmp_NS) subscription_id = int(id_node.text) with self.notification_queues_lock: if subscription_id not in self.notification_queues: tmp_queue = self.tmp_notification_queues.get( subscription_id, Queue()) tmp_queue.put(notif) self.tmp_notification_queues[subscription_id] = tmp_queue else: self.notification_queues[subscription_id].put(notif)
def subtree_filter(data, rpc): # Aqui estan los distintos componentes de la base de datos for filter_item in rpc.iter(qmap('nc') + 'filter'): filter_tree = filter_item unprunned_toreturn = data filter_elm = filter_tree logging.info(etree.tostring(unprunned_toreturn, pretty_print=True)) logging.info(etree.tostring(filter_elm, pretty_print=True)) def check_content_match(data): response = False for child in data: if not (child.text == '' or child.text is None): response = True return response def prune_descendants(data, filter): logging.info("The child " + filter.tag + " is a content match: " + str(check_content_match(filter))) if check_content_match(filter): # logging.info("Elements of the content match: ------------------") # logging.info(etree.tostring(data,pretty_print=True)) # logging.info(etree.tostring(filter, pretty_print=True)) #find content match element for child in filter: if not (child.text is '' or child.text is None): matching_elem = child # logging.info("Looking for the element " + matching_elem.tag + " , " + matching_elem.text) # Checking if the current elem matches the seached one if data.find(matching_elem.tag) is not None and data.find( matching_elem.tag).text == matching_elem.text: # logging.info("This element matches") #logging.info(etree.tostring(data,pretty_print=True)) #logging.info(etree.tostring(filter, pretty_print=True)) if len(list(filter)) > 1: matching_elem.text = '' logging.info("Containment nodes inside") logging.info(etree.tostring(data, pretty_print=True)) logging.info(etree.tostring(filter, pretty_print=True)) prune_descendants(data, filter) else: # logging.info("This element doesnt match") data.getparent().remove(data) else: for child in data: if len(list(filter)) is not 0: if filter.find(child.tag) is not None: logging.info("Element " + child.tag + " found in data, so persisting it") prune_descendants(child, filter[0]) else: logging.info("Element " + child.tag + " missing in data, deleting it") data.remove(child) prune_descendants(unprunned_toreturn, filter_elm) #logging.info(etree.tostring(unprunned_toreturn,pretty_print=True)) return unprunned_toreturn