def _open_session(self, is_server): assert is_server or self.session_id is None # The transport should be connected at this point. try: # Send hello message. self.send_hello((NC_BASE_10, NC_BASE_11), self.session_id) # Get reply reply = self._receive_message() if self.debug: logger.debug("Received HELLO") # Parse reply tree = etree.parse(io.BytesIO(reply.encode('utf-8'))) root = tree.getroot() caps = root.xpath("//nc:hello/nc:capabilities/nc:capability", namespaces=NSMAP) # Store capabilities for cap in caps: self.capabilities.add(cap.text) if NC_BASE_11 in self.capabilities: self.new_framing = True elif NC_BASE_10 not in self.capabilities: raise SessionError( "Server doesn't implement 1.0 or 1.1 of netconf") # Get session ID. try: session_id = root.xpath("//nc:hello/nc:session-id", namespaces=NSMAP)[0].text # If we are a server it is a failure to receive a session id. if is_server: raise SessionError("Client sent a session-id") self.session_id = int(session_id) except (KeyError, IndexError, AttributeError): if not is_server: raise SessionError("Server didn't supply session-id") except ValueError: raise SessionError( "Server supplied non integer session-id: {}", session_id) self.session_open = True # Create reader thread. self.reader_thread = threading.Thread( target=self._read_message_thread) self.reader_thread.daemon = True self.reader_thread.keep_running = True self.reader_thread.start() if self.debug: logger.debug("%s: Opened version %s session.", str(self), "1.1" if self.new_framing else "1.0") except Exception: self.close() raise
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 reader_handle_message(self, msg): """Handle a message, lock is already held""" 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: notification = tree.xpath("/*[name()='notification']") if not notification: raise SessionError(msg, "No rpc-reply or notification found") else: logger.info("* NOTIF received *") logger.info(etree.tostring(tree, pretty_print=True)) return for reply in replies: try: 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 wait_reply(self, msg_id, timeout=None): assert msg_id in self.rpc_out check_timeout = Timeout(timeout) self.cv.acquire() # XXX need to make sure the channel doesn't close while self.rpc_out[msg_id] is None and self.is_active(): remaining = check_timeout.remaining() self.cv.wait(remaining) if self.rpc_out[msg_id] is not None: break if check_timeout.is_expired(): raise ReplyTimeoutError( "Timeout ({}s) while waiting for RPC reply to msg-id: {}". format(timeout, msg_id)) if not self.is_active(): self.cv.release() raise SessionError("Session closed while waiting for reply") tree, reply, msg = self.rpc_out[msg_id] del self.rpc_out[msg_id] self.cv.release() error = reply.xpath("nc:rpc-error", namespaces=NSMAP) if error: raise RPCError(msg, tree, error[0]) # data = reply.xpath("nc:data", namespaces=self.nsmap) # ok = reply.xpath("nc:ok", namespaces=self.nsmap) return tree, reply, msg
def _handle_message (self, msg): """Handle a message, lock is already held""" 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('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") 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
def wait_reply(self, msg_id, timeout=None): """Wait for a reply to a given RPC message ID. :param msg_id: the RPC message ID returned from one of the async method calls :return: (Message as an lxml tree, Parsed reply content, Parsed message content). :rtype: (lxml.etree, lxml.Element, lxml.Element) :raises: RPCError, SessionError """ assert msg_id in self.rpc_out check_timeout = Timeout(timeout) self.cv.acquire() # XXX need to make sure the channel doesn't close while self.rpc_out[msg_id] is None and self.is_active(): remaining = check_timeout.remaining() self.cv.wait(remaining) if self.rpc_out[msg_id] is not None: break if check_timeout.is_expired(): raise ReplyTimeoutError( "Timeout ({}s) while waiting for RPC reply to msg-id: {}". format(timeout, msg_id)) if not self.is_active(): self.cv.release() raise SessionError("Session closed while waiting for reply") tree, reply, msg = self.rpc_out[msg_id] del self.rpc_out[msg_id] self.cv.release() error = reply.xpath("nc:rpc-error", namespaces=NSMAP) if error: raise RPCError(msg, tree, error[0]) # data = reply.xpath("nc:data", namespaces=self.nsmap) # ok = reply.xpath("nc:ok", namespaces=self.nsmap) return tree, reply, msg
def wait_reply (self, msg_id): assert msg_id in self.rpc_out self.cv.acquire() # XXX need to make sure the channel doesn't close while self.rpc_out[msg_id] is None and self.is_active(): self.cv.wait() if not self.is_active(): raise SessionError("Session closed while waiting for reply") tree, reply, msg = self.rpc_out[msg_id] del self.rpc_out[msg_id] self.cv.release() error = reply.xpath("nc:rpc-error", namespaces=NSMAP) if error: raise RPCError(msg, tree, error[0]) # data = reply.xpath("nc:data", namespaces=self.nsmap) # ok = reply.xpath("nc:ok", namespaces=self.nsmap) return tree, reply, msg
def is_reply_ready(self, msg_id): """Check whether reply is ready (or session closed)""" with self.cv: if not self.is_active(): raise SessionError("Session closed while checking for reply") return self.rpc_out[msg_id] is not None
def is_reply_ready (self, msg_id): """Check whether reply is ready (or session closed)""" if not self.is_active(): raise SessionError("Session closed while checking for reply") with self.cv: return self.is_reply_ready_locked(msg_id)
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)