Signed-off-by: Balazs Lecz <[email protected]>
---
 Makefile.am        |    5 +-
 daemons/ganeti-nld |   28 +++-
 lib/constants.py   |   48 +++++-
 lib/errors.py      |   49 +++++
 lib/nld_nld.py     |  535 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/objects.py     |   62 ++++++
 6 files changed, 722 insertions(+), 5 deletions(-)
 create mode 100644 lib/errors.py
 create mode 100644 lib/nld_nld.py
 create mode 100644 lib/objects.py

diff --git a/Makefile.am b/Makefile.am
index 9bd4073..d32bcf5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,7 +32,10 @@ pkgpython_PYTHON = \
        lib/networktables.py \
        lib/server.py \
        lib/nflog_dispatcher.py \
-       lib/nld_confd.py
+       lib/nld_confd.py \
+       lib/errors.py \
+       lib/objects.py \
+       lib/nld_nld.py
 
 nodist_pkgpython_PYTHON = \
        lib/_autoconf.py
diff --git a/daemons/ganeti-nld b/daemons/ganeti-nld
index 5f0abe6..6317663 100755
--- a/daemons/ganeti-nld
+++ b/daemons/ganeti-nld
@@ -44,6 +44,7 @@ from ganeti_nbma import constants
 from ganeti_nbma import config
 from ganeti_nbma import server
 from ganeti_nbma import nflog_dispatcher
+from ganeti_nbma import nld_nld
 from ganeti_nbma import nld_confd
 
 from ganeti import constants as gnt_constants
@@ -57,13 +58,15 @@ import ganeti.confd.client
 # Injecting ourselves in the ganeti constants
 gnt_constants.NLD = constants.NLD
 gnt_constants.DAEMONS_LOGFILES[constants.NLD] = gnt_constants.LOG_DIR + 
"nl-daemon.log"
+gnt_constants.DAEMONS_PORTS[constants.NLD] = ("udp", 1816)
 
 
 class MisroutedPacketHandler(object):
   """Callback called when a packet is received via the NFLOG target.
 
   """
-  def __init__(self, instance_node_maps):
+  def __init__(self, nld_server, instance_node_maps):
+    self.nld_server = nld_server
     self.instance_node_maps = instance_node_maps
 
   def __call__(self, i, nflog_payload):
@@ -85,14 +88,19 @@ class MisroutedPacketHandler(object):
           break
 
     if source_node:
-      # TODO: send notification to this node
       logging.debug("misrouted packet detected."
                     " [cluster: %s] [node: %s] [link: %s] [source: %s]",
                     source_cluster, source_node, source_link,
                     ip_packet.src)
+      # TODO: send NLD route invalidation request to the source node
+      request = nld_nld.NLDClientRequest(type=constants.NLD_REQ_PING)
+      self.nld_server.SendRequest(request, source_cluster, source_node)
     else:
       logging.debug("misrouted packet detected. [source: %s]",
                     ip_packet.src)
+      # TODO: remove this test code
+      request = nld_nld.NLDClientRequest(type=constants.NLD_REQ_PING)
+      self.nld_server.SendRequest(request, "default", ip_packet.src)
 
     # TODO: notify the endpoint(s) via an NLD request (preferably by iterating
     #       over the private IPs of the endpoints)
@@ -157,8 +165,10 @@ class NetworkLookupDaemon(object):
 
     # Instantiate one periodic updater per cluster
     self.updaters = []
+    self.cluster_keys = {}
     for cluster_name, cluster_options in self.config.clusters.iteritems():
       hmac_key = utils.ReadFile(cluster_options["hmac_key_file"])
+      self.cluster_keys[cluster_name] = hmac_key
       mc_list = utils.ReadFile(cluster_options["mc_list_file"]).splitlines()
       instance_node_maps[cluster_name] = {}
       self.updaters.append(
@@ -168,7 +178,19 @@ class NetworkLookupDaemon(object):
             instance_node_maps[cluster_name])
           )
 
-    misrouted_packet_callback = MisroutedPacketHandler(instance_node_maps)
+    # Instantiate NLD network request and response processers
+    # and the async UDP server
+    nld_request_processor = nld_nld.NLDRequestProcessor(self.cluster_keys)
+    nld_response_callback = nld_nld.NLDResponseCallback()
+    nld_server = nld_nld.NLDAsyncUDPServer(options.bind_address,
+                                           options.port,
+                                           nld_request_processor,
+                                           nld_response_callback,
+                                           self.cluster_keys)
+
+    # Instantiate the misrouted packet handler and its async dispatcher
+    misrouted_packet_callback = MisroutedPacketHandler(nld_server,
+                                                       instance_node_maps)
     nflog_dispatcher.AsyncNFLog(misrouted_packet_callback,
                                 log_group=self.config.nflog_queue)
 
diff --git a/lib/constants.py b/lib/constants.py
index c1277f6..bbdb61d 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2009 Google Inc.
+# Copyright (C) 2009, 2010 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@
 
 
 from ganeti_nbma import _autoconf
+from ganeti import constants as gnt_constants
 
 NLD = "ganeti-nld"
 
@@ -32,3 +33,48 @@ DEFAULT_CONF_FILE = CONF_DIR + "/common.conf"
 DEFAULT_ROUTING_TABLE = "100"
 DEFAULT_NEIGHBOUR_INTERFACE = "gtun0"
 DEFAULT_NFLOG_QUEUE = 0
+
+# NLD communication protocol related constants below
+
+# Each nld request is "salted" by the current timestamp.
+# This constants decides how many seconds of skew to accept.
+# TODO: make this a default and allow the value to be more configurable
+NLD_MAX_CLOCK_SKEW = 2 * gnt_constants.NODE_MAX_CLOCK_SKEW
+
+NLD_PROTOCOL_VERSION = 1
+
+NLD_REQ_PING = 0
+NLD_REQ_ROUTE_INVALIDATE = 1
+
+# NLD request query fields. These are used to pass parameters.
+# These must be strings rather than integers, because json-encoding
+# converts them to strings anyway, as they're used as dict-keys.
+NLD_REQQ_LINK = "0" # FIXME: rename or remove
+
+NLD_REQFIELD_NAME = "0" # FIXME: rename or remove
+
+NLD_REQS = frozenset([
+  NLD_REQ_PING,
+  NLD_REQ_ROUTE_INVALIDATE,
+  ])
+
+NLD_REPL_STATUS_OK = 0
+NLD_REPL_STATUS_ERROR = 1
+NLD_REPL_STATUS_NOTIMPLEMENTED = 2
+
+NLD_REPL_STATUSES = frozenset([
+  NLD_REPL_STATUS_OK,
+  NLD_REPL_STATUS_ERROR,
+  NLD_REPL_STATUS_NOTIMPLEMENTED,
+  ])
+
+# Magic number prepended to all nld queries.
+# This allows us to distinguish different types of nld protocols and handle
+# them. For example by changing this we can move the whole payload to be
+# compressed, or move away from json.
+NLD_MAGIC_FOURCC = 'plj0'
+
+# Timeout in seconds to expire pending query request in the nld client
+# library. We don't actually expect any answer more than 10 seconds after we
+# sent a request.
+NLD_CLIENT_EXPIRE_TIMEOUT = 10
diff --git a/lib/errors.py b/lib/errors.py
new file mode 100644
index 0000000..06d7e93
--- /dev/null
+++ b/lib/errors.py
@@ -0,0 +1,49 @@
+#
+#
+
+# Copyright (C) 20010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Ganeti NLD exception handling"""
+
+from ganeti import errors as ganeti_errors
+
+
+class NLDRequestError(ganeti_errors.GenericError):
+  """A request error in Ganeti NLD.
+
+  Events that should make nld abort the current request and proceed serving
+  different ones.
+
+  """
+
+
+class NLDClientError(ganeti_errors.GenericError):
+  """A magic fourcc error in Ganeti NLD.
+
+  Errors in the NLD client library.
+
+  """
+
+
+class NLDMagicError(ganeti_errors.GenericError):
+  """A magic fourcc error in Ganeti NLD.
+
+  Errors processing the fourcc in Ganeti NLD datagrams.
+
+  """
diff --git a/lib/nld_nld.py b/lib/nld_nld.py
new file mode 100644
index 0000000..bc8ac48
--- /dev/null
+++ b/lib/nld_nld.py
@@ -0,0 +1,535 @@
+#
+#
+
+# Copyright (C) 2010 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Ganeti NLD->NLD communication related functions
+
+"""
+
+# pylint: disable-msg=E0203
+
+# E0203: Access to member %r before its definition, since we use
+# objects.py which doesn't explicitely initialise its members
+
+
+import logging
+import time
+
+from ganeti_nbma import constants
+from ganeti_nbma import errors
+from ganeti_nbma import objects
+
+from ganeti import errors as gnt_errors
+from ganeti import objects as gnt_objects
+from ganeti import serializer
+from ganeti import daemon
+from ganeti import utils
+
+
+_FOURCC_LEN = 4
+
+
+def PackMagic(payload):
+  """Prepend the NLD magic fourcc to a payload.
+
+  """
+  return ''.join([constants.NLD_MAGIC_FOURCC, payload])
+
+
+def UnpackMagic(payload):
+  """Unpack and check the NLD magic fourcc from a payload.
+
+  """
+  if len(payload) < _FOURCC_LEN:
+    raise errors.NLDMagicError("UDP payload too short to contain the"
+                                 " fourcc code")
+
+  magic_number = payload[:_FOURCC_LEN]
+  if magic_number != constants.NLD_MAGIC_FOURCC:
+    raise errors.NLDMagicError("UDP payload contains an unkown fourcc")
+
+  return payload[_FOURCC_LEN:]
+
+
+class NLDQuery(object):
+  """NLD Query base class.
+
+  """
+  def Exec(self, query): # pylint: disable-msg=R0201,W0613
+    """Process a single UDP request from a client.
+
+    Different queries should override this function, which by defaults returns
+    a "non-implemented" answer.
+
+    @type query: (undefined)
+    @param query: NLDRequest 'query' field
+    @rtype: (integer, undefined)
+    @return: status and answer to give to the client
+
+    """
+    status = constants.NLD_REPL_STATUS_NOTIMPLEMENTED
+    answer = 'not implemented'
+    return status, answer
+
+
+class PingQuery(NLDQuery):
+  """An empty NLD query.
+
+  It will return success on an empty argument, and an error on any other
+  argument.
+
+  """
+  def Exec(self, query):
+    """PingQuery main execution.
+
+    """
+    if query is None:
+      status = constants.NLD_REPL_STATUS_OK
+      answer = 'ok'
+    else:
+      status = constants.NLD_REPL_STATUS_ERROR
+      answer = 'non-empty ping query'
+
+    return status, answer
+
+
+class RouteInvalidateQuery(NLDQuery):
+  # TODO: implement this
+  pass
+
+
+class NLDRequestProcessor(object):
+  """A processor for NLD requests.
+
+  """
+  DISPATCH_TABLE = {
+    constants.NLD_REQ_PING: PingQuery,
+    constants.NLD_REQ_ROUTE_INVALIDATE: RouteInvalidateQuery,
+    }
+
+  def __init__(self, cluster_keys):
+    """Constructor for NLDRequestProcessor
+
+    """
+    # TODO: make this private
+    self.cluster_keys = cluster_keys
+    assert \
+      not constants.NLD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
+      "DISPATCH_TABLE is unaligned with NLD_REQS"
+
+  def ExecQuery(self, payload, ip, port):
+    """Process a single NLD request.
+
+    @type payload: string
+    @param payload: request raw data
+    @type ip: string
+    @param ip: source ip address
+    @param port: integer
+    @type port: source port
+
+    """
+    try:
+      cluster_name, request = self.ExtractRequest(payload)
+      reply, rsalt = self.ProcessRequest(request)
+      payload_out = self.PackReply(reply, rsalt, cluster_name)
+      return payload_out
+    except errors.NLDRequestError, err:
+      logging.info('Ignoring broken query from %s:%d: %s', ip, port, err)
+      return None
+
+  def ExtractRequest(self, payload):
+    """Extracts an NLDRequest object from a serialized hmac signed string.
+
+    This function also performs signature/timestamp validation.
+
+    """
+    current_time = time.time()
+    logging.debug("Extracting request with size: %d", len(payload))
+    try:
+      (message, salt) = serializer.LoadSigned(payload,
+                                              key=self.cluster_keys.get)
+    except gnt_errors.SignatureError, err:
+      msg = "invalid signature: %s" % err
+      raise errors.NLDRequestError(msg)
+    try:
+      message_timestamp = int(salt)
+    except (ValueError, TypeError):
+      msg = "non-integer timestamp: %s" % salt
+      raise errors.NLDRequestError(msg)
+
+    skew = abs(current_time - message_timestamp)
+    if skew > constants.NLD_MAX_CLOCK_SKEW:
+      msg = "outside time range (skew: %d)" % skew
+      raise errors.NLDRequestError(msg)
+
+    try:
+      cluster_name = message["cluster"]
+    except KeyError:
+      raise errors.NLDRequestError("Cluster name is missing from NLD request")
+
+    try:
+      request = objects.NLDRequest.FromDict(message)
+    except AttributeError, err:
+      raise errors.NLDRequestError('%s' % err)
+
+    return cluster_name, request
+
+  def ProcessRequest(self, request):
+    """Process one NLDRequest, and produce an answer
+
+    @type request: L{objects.NLDRequest}
+    @rtype: (L{objects.NLDReply}, string)
+    @return: tuple of reply and salt to add to the signature
+
+    """
+    logging.debug("Processing request: %s", request)
+    if request.protocol != constants.NLD_PROTOCOL_VERSION:
+      msg = "wrong protocol version %d" % request.protocol
+      raise errors.NLDRequestError(msg)
+
+    if request.type not in constants.NLD_REQS:
+      msg = "wrong request type %d" % request.type
+      raise errors.NLDRequestError(msg)
+
+    rsalt = request.rsalt
+    if not rsalt:
+      msg = "missing requested salt"
+      raise errors.NLDRequestError(msg)
+
+    query_object = self.DISPATCH_TABLE[request.type]()
+    status, answer = query_object.Exec(request.query)
+    reply = objects.NLDReply(
+      protocol=constants.NLD_PROTOCOL_VERSION,
+      is_request=False, # TODO: move this obvious initialization into the
+                        # __init__ of NLDReply?
+      status=status,
+      answer=answer,
+      )
+
+    logging.debug("Sending reply: %s", reply)
+
+    return (reply, rsalt)
+
+  def PackReply(self, reply, rsalt, cluster_name):
+    """Serialize and sign the given reply, with salt rsalt
+
+    @type reply: L{objects.NLDReply}
+    @type rsalt: string
+    @param cluster_name: name of the cluster
+
+    """
+    message = reply.ToDict()
+    message['cluster'] = cluster_name
+    return serializer.DumpSigned(
+      message,
+      self.cluster_keys[cluster_name],
+      salt=rsalt,
+      key_selector=cluster_name
+      )
+
+
+class NLDAsyncUDPServer(daemon.AsyncUDPSocket):
+  """The NLD UDP server, suitable for use with asyncore.
+
+  """
+  def __init__(self, bind_address, port, processor, callback, cluster_keys):
+    """Constructor for NLDAsyncUDPServer
+
+    @type bind_address: string
+    @param bind_address: socket bind address ('' for all)
+    @type port: int
+    @param port: udp port
+    @type processor: L{NLDRequestProcessor}
+    @param processor: NLDRequestProcessor to use to handle queries
+    @param callback: NLDResponseCallback to use to handle responses
+    @param cluster_keys: dictinary with the cluster hmac keys
+
+    """
+    # TODO: make these memebers private?
+    daemon.AsyncUDPSocket.__init__(self)
+    self.bind_address = bind_address
+    self.port = port
+    self.processor = processor
+    self.bind((bind_address, port))
+    self._callback = callback
+    self._cluster_keys = cluster_keys
+    # TODO: rename this to sent_requests?
+    self._requests = {}
+    self._expire_requests = []
+
+    logging.debug("listening on ('%s':%d)", bind_address, port)
+
+  # this method is overriding the daemon.AsyncUDPSocket method
+  def handle_datagram(self, payload_in, ip, port):
+    try:
+      payload = UnpackMagic(payload_in)
+    except errors.NLDMagicError, err:
+      logging.debug(err)
+      return
+
+    # TODO: We are converting the JSON payload multiple times.
+    # It can be quite wasteful. We are not expecting high traffic,
+    # but there might be opportunities to optimize it.
+    signed_message = serializer.LoadJson(payload)
+    message = serializer.LoadJson(signed_message['msg'])
+
+    message_is_request = message.get('is_request', None)
+    if message_is_request is None:
+      logging.error("Message request/response discriminator field is missing."
+                    " Message: [%s]", message)
+      return
+
+    if message_is_request:
+      self.HandleRequest(payload, ip, port)
+    else:
+      self.HandleResponse(payload, ip, port)
+
+  def HandleRequest(self, payload, ip, port):
+    answer =  self.processor.ExecQuery(payload, ip, port)
+    if answer is not None:
+      try:
+        self.enqueue_send(ip, port, PackMagic(answer))
+      except gnt_errors.UdpDataSizeError:
+        logging.error("Reply too big to fit in an udp packet.")
+
+  def _PackRequest(self, request, cluster_name, timestamp=None):
+    """Prepare a request to be sent on the wire.
+
+    This function puts a proper salt in an NLD request and adds the correct
+    magic number.
+
+    """
+    if timestamp is None:
+      timestamp = time.time()
+    tstamp = '%d' % timestamp
+    req = serializer.DumpSignedJson(request.ToDict(),
+                                    self._cluster_keys[cluster_name],
+                                    tstamp,
+                                    key_selector=cluster_name)
+    return PackMagic(req)
+
+  def _UnpackReply(self, payload):
+    (dict_answer, salt) = serializer.LoadSignedJson(payload,
+                                                    key=self._cluster_keys.get)
+    answer = objects.NLDReply.FromDict(dict_answer)
+    return answer, salt
+
+  # TODO: make this private
+  def ExpireRequests(self):
+    """Delete all the expired requests.
+
+    """
+    now = time.time()
+    while self._expire_requests:
+      expire_time, rsalt = self._expire_requests[0]
+      if now >= expire_time:
+        self._expire_requests.pop(0)
+        (request, args) = self._requests[rsalt]
+        del self._requests[rsalt]
+        client_reply = NLDUpcallPayload(salt=rsalt,
+                                        type=UPCALL_EXPIRE,
+                                        orig_request=request,
+                                        extra_args=args,
+                                        client=self,
+                                        )
+        self._callback(client_reply)
+      else:
+        break
+
+  def SendRequest(self, request, cluster_name, destination, args=None):
+    """Send an NLD request to another NLD instance
+
+    @type request: L{objects.NLDRequest}
+    @param request: the request to send
+    @param cluster_name: name of the cluster
+    @param destination: the address of the target NLD instance
+    @type args: tuple
+    @keyword args: additional callback arguments
+
+    """
+    request.cluster = cluster_name
+
+    if not request.rsalt:
+      raise errors.NLDClientError("Missing request rsalt")
+
+    self.ExpireRequests()
+    if request.rsalt in self._requests:
+      raise errors.NLDClientError("Duplicate request rsalt")
+
+    if request.type not in constants.NLD_REQS:
+      raise errors.NLDClientError("Invalid request type")
+
+    now = time.time()
+    payload = self._PackRequest(request, cluster_name, timestamp=now)
+
+    try:
+      self.enqueue_send(destination, self.port, payload)
+    except gnt_errors.UdpDataSizeError:
+      raise errors.NLDClientError("Request too big")
+
+    self._requests[request.rsalt] = (request, args)
+    expire_time = now + constants.NLD_CLIENT_EXPIRE_TIMEOUT
+    self._expire_requests.append((expire_time, request.rsalt))
+
+  # TODO: make this private
+  def HandleResponse(self, payload, ip, port):
+    """Asynchronous handler for an NLD reply
+
+    Call the relevant callback associated with the original request.
+
+    """
+    try:
+      try:
+        answer, salt = self._UnpackReply(payload)
+      except (gnt_errors.SignatureError, errors.NLDMagicError), err:
+        if self._logger:
+          self._logger.debug("Discarding broken package: %s" % err)
+        return
+
+      try:
+        (request, args) = self._requests[salt]
+      except KeyError:
+        if self._logger:
+          self._logger.debug("Discarding unknown (expired?) reply: %s" % err)
+        return
+
+      client_reply = NLDUpcallPayload(salt=salt,
+                                      type=UPCALL_REPLY,
+                                      server_reply=answer,
+                                      orig_request=request,
+                                      server_ip=ip,
+                                      server_port=port,
+                                      extra_args=args,
+                                      client=self,
+                                      )
+      self._callback(client_reply)
+
+    finally:
+      self.ExpireRequests()
+
+
+# UPCALL_REPLY: server reply upcall
+# has all NLDUpcallPayload fields populated
+UPCALL_REPLY = 1
+# UPCALL_EXPIRE: internal library request expire
+# has only salt, type, orig_request and extra_args
+UPCALL_EXPIRE = 2
+NLD_UPCALL_TYPES = frozenset([
+  UPCALL_REPLY,
+  UPCALL_EXPIRE,
+  ])
+
+
+class NLDUpcallPayload(gnt_objects.ConfigObject):
+  """Callback argument for NLD replies
+
+  @type salt: string
+  @ivar salt: salt associated with the query
+  @type type: one of client.NLD_UPCALL_TYPES
+  @ivar type: upcall type (server reply, expired request, ...)
+  @type orig_request: L{objects.NLDRequest}
+  @ivar orig_request: original request
+  @type server_reply: L{objects.NLDReply}
+  @ivar server_reply: server reply
+  @type server_ip: string
+  @ivar server_ip: answering server ip address
+  @type server_port: int
+  @ivar server_port: answering server port
+  @type extra_args: any
+  @ivar extra_args: 'args' argument of the SendRequest function
+  @type client: L{NLDClient}
+  @ivar client: current NLD client instance
+
+  """
+  __slots__ = [
+    "salt",
+    "type",
+    "orig_request",
+    "server_reply",
+    "server_ip",
+    "server_port",
+    "extra_args",
+    "client",
+    ]
+
+
+class NLDClientRequest(objects.NLDRequest):
+  """This is the client-side version of NLDRequest.
+
+  This version of the class helps creating requests, on the client side, by
+  filling in some default values.
+
+  """
+  def __init__(self, **kwargs):
+    objects.NLDRequest.__init__(self, **kwargs)
+    self.is_request = True
+    if not self.rsalt:
+      self.rsalt = utils.NewUUID()
+    if not self.protocol:
+      self.protocol = constants.NLD_PROTOCOL_VERSION
+    if self.type not in constants.NLD_REQS:
+      raise errors.NLDClientError("Invalid request type")
+
+
+class NLDResponseCallback(object):
+  """Callback for NLD responses.
+
+  """
+  def __init__(self):
+    self.dispatch_table = {
+      constants.NLD_REQ_PING:
+        self.HandlePingResponse,
+      constants.NLD_REQ_ROUTE_INVALIDATE:
+        self.HandleRouteInvalidate,
+    }
+
+  @staticmethod
+  def HandlePingResponse(up):
+    """Handle response to a ping request
+
+    """
+    logging.debug("Received a ping response: [%s]", up.server_reply.answer)
+
+  @staticmethod
+  def HandleRouteInvalidate(up):
+    # TODO: implement this
+    # We don't really expect an answer to a route invalidate request,
+    # maybe a confirmation that it was received and accepted (or rejected)
+    logging.debug("Got a reply to a route invalidate request")
+
+  def __call__(self, up):
+    """NLD response callback.
+
+    @type up: L{NLDUpcallPayload}
+    @param up: upper callback
+
+    """
+    if up.type == UPCALL_REPLY:
+      if up.server_reply.status != constants.NLD_REPL_STATUS_OK:
+        logging.warning("Received error '%s' to NLD request %s",
+                        up.server_reply.answer, up.orig_request)
+        return
+
+      rtype = up.orig_request.type
+      try:
+        dispatcher = self.dispatch_table[rtype]
+      except KeyError, err: # pylint: disable-msg=W0612
+        logging.warning("Unhandled NLD response type: %s", rtype)
+      dispatcher(up)
diff --git a/lib/objects.py b/lib/objects.py
new file mode 100644
index 0000000..da0f5c3
--- /dev/null
+++ b/lib/objects.py
@@ -0,0 +1,62 @@
+#
+#
+
+# Copyright (C) 2006, 2007 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Transportable objects for Ganeti NLD.
+
+"""
+
+from ganeti import objects as gnt_objects
+
+
+class NLDRequest(gnt_objects.ConfigObject):
+  """Object holding an NLD request.
+
+  @ivar protocol: NLD protocol version
+  @ivar type: NLD query type
+  @ivar query: query request
+  @ivar rsalt: requested reply salt
+
+  """
+  __slots__ = [
+    "protocol",
+    "is_request",
+    "cluster",
+    "type",
+    "query",
+    "rsalt",
+    ]
+
+
+class NLDReply(gnt_objects.ConfigObject):
+  """Object holding an NLD reply.
+
+  @ivar protocol: NLD protocol version
+  @ivar status: reply status code (ok, error)
+  @ivar answer: NLD query reply
+
+  """
+  __slots__ = [
+    "protocol",
+    "is_request",
+    "cluster",
+    "status",
+    "answer",
+    ]
-- 
1.7.0.1

Reply via email to