Statistics
| Branch: | Tag: | Revision:

root / lib / confd / client.py @ 392ca296

History | View | Annotate | Download (12.3 kB)

1 e4ccf6cd Guido Trotter
#
2 e4ccf6cd Guido Trotter
#
3 e4ccf6cd Guido Trotter
4 e4ccf6cd Guido Trotter
# Copyright (C) 2009 Google Inc.
5 e4ccf6cd Guido Trotter
#
6 e4ccf6cd Guido Trotter
# This program is free software; you can redistribute it and/or modify
7 e4ccf6cd Guido Trotter
# it under the terms of the GNU General Public License as published by
8 e4ccf6cd Guido Trotter
# the Free Software Foundation; either version 2 of the License, or
9 e4ccf6cd Guido Trotter
# (at your option) any later version.
10 e4ccf6cd Guido Trotter
#
11 e4ccf6cd Guido Trotter
# This program is distributed in the hope that it will be useful, but
12 e4ccf6cd Guido Trotter
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 e4ccf6cd Guido Trotter
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 e4ccf6cd Guido Trotter
# General Public License for more details.
15 e4ccf6cd Guido Trotter
#
16 e4ccf6cd Guido Trotter
# You should have received a copy of the GNU General Public License
17 e4ccf6cd Guido Trotter
# along with this program; if not, write to the Free Software
18 e4ccf6cd Guido Trotter
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 e4ccf6cd Guido Trotter
# 02110-1301, USA.
20 e4ccf6cd Guido Trotter
21 e4ccf6cd Guido Trotter
22 e4ccf6cd Guido Trotter
"""Ganeti confd client
23 e4ccf6cd Guido Trotter

24 cf7b0cc4 Guido Trotter
Clients can use the confd client library to send requests to a group of master
25 cf7b0cc4 Guido Trotter
candidates running confd. The expected usage is through the asyncore framework,
26 cf7b0cc4 Guido Trotter
by sending queries, and asynchronously receiving replies through a callback.
27 cf7b0cc4 Guido Trotter

28 cf7b0cc4 Guido Trotter
This way the client library doesn't ever need to "wait" on a particular answer,
29 cf7b0cc4 Guido Trotter
and can proceed even if some udp packets are lost. It's up to the user to
30 cf7b0cc4 Guido Trotter
reschedule queries if they haven't received responses and they need them.
31 cf7b0cc4 Guido Trotter

32 cf7b0cc4 Guido Trotter
Example usage:
33 cf7b0cc4 Guido Trotter
  client = ConfdClient(...) # includes callback specification
34 cf7b0cc4 Guido Trotter
  req = confd_client.ConfdClientRequest(type=constants.CONFD_REQ_PING)
35 cf7b0cc4 Guido Trotter
  client.SendRequest(req)
36 cf7b0cc4 Guido Trotter
  # then make sure your client calls asyncore.loop() or daemon.Mainloop.Run()
37 cf7b0cc4 Guido Trotter
  # ... wait ...
38 cf7b0cc4 Guido Trotter
  # And your callback will be called by asyncore, when your query gets a
39 cf7b0cc4 Guido Trotter
  # response, or when it expires.
40 cf7b0cc4 Guido Trotter

41 392ca296 Guido Trotter
You can use the provided ConfdFilterCallback to act as a filter, only passing
42 392ca296 Guido Trotter
"newer" answer to your callback, and filtering out outdated ones, or ones
43 392ca296 Guido Trotter
confirming what you already got.
44 392ca296 Guido Trotter

45 e4ccf6cd Guido Trotter
"""
46 e4ccf6cd Guido Trotter
import socket
47 e4ccf6cd Guido Trotter
import time
48 e4ccf6cd Guido Trotter
import random
49 e4ccf6cd Guido Trotter
50 e4ccf6cd Guido Trotter
from ganeti import utils
51 e4ccf6cd Guido Trotter
from ganeti import constants
52 e4ccf6cd Guido Trotter
from ganeti import objects
53 e4ccf6cd Guido Trotter
from ganeti import serializer
54 e4ccf6cd Guido Trotter
from ganeti import daemon # contains AsyncUDPSocket
55 e4ccf6cd Guido Trotter
from ganeti import errors
56 e4ccf6cd Guido Trotter
from ganeti import confd
57 e4ccf6cd Guido Trotter
58 e4ccf6cd Guido Trotter
59 e4ccf6cd Guido Trotter
class ConfdAsyncUDPClient(daemon.AsyncUDPSocket):
60 e4ccf6cd Guido Trotter
  """Confd udp asyncore client
61 e4ccf6cd Guido Trotter

62 e4ccf6cd Guido Trotter
  This is kept separate from the main ConfdClient to make sure it's easy to
63 e4ccf6cd Guido Trotter
  implement a non-asyncore based client library.
64 e4ccf6cd Guido Trotter

65 e4ccf6cd Guido Trotter
  """
66 e4ccf6cd Guido Trotter
  def __init__(self, client):
67 e4ccf6cd Guido Trotter
    """Constructor for ConfdAsyncUDPClient
68 e4ccf6cd Guido Trotter

69 e4ccf6cd Guido Trotter
    @type client: L{ConfdClient}
70 e4ccf6cd Guido Trotter
    @param client: client library, to pass the datagrams to
71 e4ccf6cd Guido Trotter

72 e4ccf6cd Guido Trotter
    """
73 e4ccf6cd Guido Trotter
    daemon.AsyncUDPSocket.__init__(self)
74 e4ccf6cd Guido Trotter
    self.client = client
75 e4ccf6cd Guido Trotter
76 e4ccf6cd Guido Trotter
  # this method is overriding a daemon.AsyncUDPSocket method
77 e4ccf6cd Guido Trotter
  def handle_datagram(self, payload, ip, port):
78 e4ccf6cd Guido Trotter
    self.client.HandleResponse(payload, ip, port)
79 e4ccf6cd Guido Trotter
80 e4ccf6cd Guido Trotter
81 e4ccf6cd Guido Trotter
class ConfdClient:
82 e4ccf6cd Guido Trotter
  """Send queries to confd, and get back answers.
83 e4ccf6cd Guido Trotter

84 e4ccf6cd Guido Trotter
  Since the confd model works by querying multiple master candidates, and
85 e4ccf6cd Guido Trotter
  getting back answers, this is an asynchronous library. It can either work
86 e4ccf6cd Guido Trotter
  through asyncore or with your own handling.
87 e4ccf6cd Guido Trotter

88 e4ccf6cd Guido Trotter
  """
89 a3db74e4 Guido Trotter
  def __init__(self, hmac_key, peers, callback, port=None, logger=None):
90 e4ccf6cd Guido Trotter
    """Constructor for ConfdClient
91 e4ccf6cd Guido Trotter

92 e4ccf6cd Guido Trotter
    @type hmac_key: string
93 e4ccf6cd Guido Trotter
    @param hmac_key: hmac key to talk to confd
94 e4ccf6cd Guido Trotter
    @type peers: list
95 e4ccf6cd Guido Trotter
    @param peers: list of peer nodes
96 96e03b0b Guido Trotter
    @type callback: f(L{ConfdUpcallPayload})
97 96e03b0b Guido Trotter
    @param callback: function to call when getting answers
98 7d20c647 Guido Trotter
    @type port: integer
99 7d20c647 Guido Trotter
    @keyword port: confd port (default: use GetDaemonPort)
100 a3db74e4 Guido Trotter
    @type logger: L{logging.Logger}
101 a3db74e4 Guido Trotter
    @keyword logger: optional logger for internal conditions
102 e4ccf6cd Guido Trotter

103 e4ccf6cd Guido Trotter
    """
104 e4ccf6cd Guido Trotter
    if not isinstance(peers, list):
105 e4ccf6cd Guido Trotter
      raise errors.ProgrammerError("peers must be a list")
106 96e03b0b Guido Trotter
    if not callable(callback):
107 96e03b0b Guido Trotter
      raise errors.ProgrammerError("callback must be callable")
108 e4ccf6cd Guido Trotter
109 e4ccf6cd Guido Trotter
    self._peers = peers
110 e4ccf6cd Guido Trotter
    self._hmac_key = hmac_key
111 e4ccf6cd Guido Trotter
    self._socket = ConfdAsyncUDPClient(self)
112 96e03b0b Guido Trotter
    self._callback = callback
113 7d20c647 Guido Trotter
    self._confd_port = port
114 a3db74e4 Guido Trotter
    self._logger = logger
115 96e03b0b Guido Trotter
    self._requests = {}
116 96e03b0b Guido Trotter
    self._expire_requests = []
117 7d20c647 Guido Trotter
118 7d20c647 Guido Trotter
    if self._confd_port is None:
119 7d20c647 Guido Trotter
      self._confd_port = utils.GetDaemonPort(constants.CONFD)
120 e4ccf6cd Guido Trotter
121 e4ccf6cd Guido Trotter
  def _PackRequest(self, request, now=None):
122 e4ccf6cd Guido Trotter
    """Prepare a request to be sent on the wire.
123 e4ccf6cd Guido Trotter

124 e4ccf6cd Guido Trotter
    This function puts a proper salt in a confd request, puts the proper salt,
125 e4ccf6cd Guido Trotter
    and adds the correct magic number.
126 e4ccf6cd Guido Trotter

127 e4ccf6cd Guido Trotter
    """
128 e4ccf6cd Guido Trotter
    if now is None:
129 e4ccf6cd Guido Trotter
      now = time.time()
130 e4ccf6cd Guido Trotter
    tstamp = '%d' % now
131 e4ccf6cd Guido Trotter
    req = serializer.DumpSignedJson(request.ToDict(), self._hmac_key, tstamp)
132 e4ccf6cd Guido Trotter
    return confd.PackMagic(req)
133 e4ccf6cd Guido Trotter
134 e4ccf6cd Guido Trotter
  def _UnpackReply(self, payload):
135 e4ccf6cd Guido Trotter
    in_payload = confd.UnpackMagic(payload)
136 c103d7ae Guido Trotter
    (dict_answer, salt) = serializer.LoadSignedJson(in_payload, self._hmac_key)
137 c103d7ae Guido Trotter
    answer = objects.ConfdReply.FromDict(dict_answer)
138 e4ccf6cd Guido Trotter
    return answer, salt
139 e4ccf6cd Guido Trotter
140 96e03b0b Guido Trotter
  def ExpireRequests(self):
141 96e03b0b Guido Trotter
    """Delete all the expired requests.
142 e4ccf6cd Guido Trotter

143 e4ccf6cd Guido Trotter
    """
144 e4ccf6cd Guido Trotter
    now = time.time()
145 96e03b0b Guido Trotter
    while self._expire_requests:
146 96e03b0b Guido Trotter
      expire_time, rsalt = self._expire_requests[0]
147 e4ccf6cd Guido Trotter
      if now >= expire_time:
148 96e03b0b Guido Trotter
        self._expire_requests.pop(0)
149 96e03b0b Guido Trotter
        (request, args) = self._requests[rsalt]
150 96e03b0b Guido Trotter
        del self._requests[rsalt]
151 96e03b0b Guido Trotter
        client_reply = ConfdUpcallPayload(salt=rsalt,
152 96e03b0b Guido Trotter
                                          type=UPCALL_EXPIRE,
153 96e03b0b Guido Trotter
                                          orig_request=request,
154 96e03b0b Guido Trotter
                                          extra_args=args)
155 96e03b0b Guido Trotter
        self._callback(client_reply)
156 e4ccf6cd Guido Trotter
      else:
157 e4ccf6cd Guido Trotter
        break
158 e4ccf6cd Guido Trotter
159 96e03b0b Guido Trotter
  def SendRequest(self, request, args=None, coverage=None):
160 e4ccf6cd Guido Trotter
    """Send a confd request to some MCs
161 e4ccf6cd Guido Trotter

162 e4ccf6cd Guido Trotter
    @type request: L{objects.ConfdRequest}
163 e4ccf6cd Guido Trotter
    @param request: the request to send
164 e4ccf6cd Guido Trotter
    @type args: tuple
165 90469357 Guido Trotter
    @keyword args: additional callback arguments
166 e4ccf6cd Guido Trotter
    @type coverage: integer
167 e4ccf6cd Guido Trotter
    @keyword coverage: number of remote nodes to contact
168 e4ccf6cd Guido Trotter

169 e4ccf6cd Guido Trotter
    """
170 e4ccf6cd Guido Trotter
    if coverage is None:
171 e4ccf6cd Guido Trotter
      coverage = min(len(self._peers), constants.CONFD_DEFAULT_REQ_COVERAGE)
172 e4ccf6cd Guido Trotter
173 e4ccf6cd Guido Trotter
    if coverage > len(self._peers):
174 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Not enough MCs known to provide the"
175 e4ccf6cd Guido Trotter
                                    " desired coverage")
176 e4ccf6cd Guido Trotter
177 e4ccf6cd Guido Trotter
    if not request.rsalt:
178 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Missing request rsalt")
179 e4ccf6cd Guido Trotter
180 96e03b0b Guido Trotter
    self.ExpireRequests()
181 96e03b0b Guido Trotter
    if request.rsalt in self._requests:
182 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Duplicate request rsalt")
183 e4ccf6cd Guido Trotter
184 e4ccf6cd Guido Trotter
    if request.type not in constants.CONFD_REQS:
185 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")
186 e4ccf6cd Guido Trotter
187 e4ccf6cd Guido Trotter
    random.shuffle(self._peers)
188 e4ccf6cd Guido Trotter
    targets = self._peers[:coverage]
189 e4ccf6cd Guido Trotter
190 e4ccf6cd Guido Trotter
    now = time.time()
191 e4ccf6cd Guido Trotter
    payload = self._PackRequest(request, now=now)
192 e4ccf6cd Guido Trotter
193 e4ccf6cd Guido Trotter
    for target in targets:
194 e4ccf6cd Guido Trotter
      try:
195 e4ccf6cd Guido Trotter
        self._socket.enqueue_send(target, self._confd_port, payload)
196 e4ccf6cd Guido Trotter
      except errors.UdpDataSizeError:
197 e4ccf6cd Guido Trotter
        raise errors.ConfdClientError("Request too big")
198 e4ccf6cd Guido Trotter
199 96e03b0b Guido Trotter
    self._requests[request.rsalt] = (request, args)
200 e4ccf6cd Guido Trotter
    expire_time = now + constants.CONFD_CLIENT_EXPIRE_TIMEOUT
201 96e03b0b Guido Trotter
    self._expire_requests.append((expire_time, request.rsalt))
202 e4ccf6cd Guido Trotter
203 e4ccf6cd Guido Trotter
  def HandleResponse(self, payload, ip, port):
204 e4ccf6cd Guido Trotter
    """Asynchronous handler for a confd reply
205 e4ccf6cd Guido Trotter

206 e4ccf6cd Guido Trotter
    Call the relevant callback associated to the current request.
207 e4ccf6cd Guido Trotter

208 e4ccf6cd Guido Trotter
    """
209 e4ccf6cd Guido Trotter
    try:
210 e4ccf6cd Guido Trotter
      try:
211 e4ccf6cd Guido Trotter
        answer, salt = self._UnpackReply(payload)
212 a3db74e4 Guido Trotter
      except (errors.SignatureError, errors.ConfdMagicError), err:
213 a3db74e4 Guido Trotter
        if self._logger:
214 a3db74e4 Guido Trotter
          self._logger.debug("Discarding broken package: %s" % err)
215 e4ccf6cd Guido Trotter
        return
216 e4ccf6cd Guido Trotter
217 e4ccf6cd Guido Trotter
      try:
218 96e03b0b Guido Trotter
        (request, args) = self._requests[salt]
219 e4ccf6cd Guido Trotter
      except KeyError:
220 a3db74e4 Guido Trotter
        if self._logger:
221 a3db74e4 Guido Trotter
          self._logger.debug("Discarding unknown (expired?) reply: %s" % err)
222 96e03b0b Guido Trotter
        return
223 96e03b0b Guido Trotter
224 96e03b0b Guido Trotter
      client_reply = ConfdUpcallPayload(salt=salt,
225 96e03b0b Guido Trotter
                                        type=UPCALL_REPLY,
226 96e03b0b Guido Trotter
                                        server_reply=answer,
227 96e03b0b Guido Trotter
                                        orig_request=request,
228 96e03b0b Guido Trotter
                                        server_ip=ip,
229 96e03b0b Guido Trotter
                                        server_port=port,
230 96e03b0b Guido Trotter
                                        extra_args=args)
231 96e03b0b Guido Trotter
      self._callback(client_reply)
232 e4ccf6cd Guido Trotter
233 e4ccf6cd Guido Trotter
    finally:
234 96e03b0b Guido Trotter
      self.ExpireRequests()
235 96e03b0b Guido Trotter
236 96e03b0b Guido Trotter
237 96e03b0b Guido Trotter
# UPCALL_REPLY: server reply upcall
238 96e03b0b Guido Trotter
# has all ConfdUpcallPayload fields populated
239 96e03b0b Guido Trotter
UPCALL_REPLY = 1
240 96e03b0b Guido Trotter
# UPCALL_EXPIRE: internal library request expire
241 96e03b0b Guido Trotter
# has only salt, type, orig_request and extra_args
242 96e03b0b Guido Trotter
UPCALL_EXPIRE = 2
243 96e03b0b Guido Trotter
CONFD_UPCALL_TYPES = frozenset([
244 96e03b0b Guido Trotter
  UPCALL_REPLY,
245 96e03b0b Guido Trotter
  UPCALL_EXPIRE,
246 96e03b0b Guido Trotter
  ])
247 96e03b0b Guido Trotter
248 96e03b0b Guido Trotter
249 96e03b0b Guido Trotter
class ConfdUpcallPayload(objects.ConfigObject):
250 96e03b0b Guido Trotter
  """Callback argument for confd replies
251 96e03b0b Guido Trotter

252 96e03b0b Guido Trotter
  @type salt: string
253 96e03b0b Guido Trotter
  @ivar salt: salt associated with the query
254 96e03b0b Guido Trotter
  @type type: one of confd.client.CONFD_UPCALL_TYPES
255 96e03b0b Guido Trotter
  @ivar type: upcall type (server reply, expired request, ...)
256 96e03b0b Guido Trotter
  @type orig_request: L{objects.ConfdRequest}
257 96e03b0b Guido Trotter
  @ivar orig_request: original request
258 96e03b0b Guido Trotter
  @type server_reply: L{objects.ConfdReply}
259 96e03b0b Guido Trotter
  @ivar server_reply: server reply
260 96e03b0b Guido Trotter
  @type server_ip: string
261 96e03b0b Guido Trotter
  @ivar server_ip: answering server ip address
262 96e03b0b Guido Trotter
  @type server_port: int
263 96e03b0b Guido Trotter
  @ivar server_port: answering server port
264 96e03b0b Guido Trotter
  @type extra_args: any
265 96e03b0b Guido Trotter
  @ivar extra_args: 'args' argument of the SendRequest function
266 96e03b0b Guido Trotter

267 96e03b0b Guido Trotter
  """
268 96e03b0b Guido Trotter
  __slots__ = [
269 96e03b0b Guido Trotter
    "salt",
270 96e03b0b Guido Trotter
    "type",
271 96e03b0b Guido Trotter
    "orig_request",
272 96e03b0b Guido Trotter
    "server_reply",
273 96e03b0b Guido Trotter
    "server_ip",
274 96e03b0b Guido Trotter
    "server_port",
275 96e03b0b Guido Trotter
    "extra_args",
276 96e03b0b Guido Trotter
    ]
277 e4ccf6cd Guido Trotter
278 e4ccf6cd Guido Trotter
279 e4ccf6cd Guido Trotter
class ConfdClientRequest(objects.ConfdRequest):
280 e4ccf6cd Guido Trotter
  """This is the client-side version of ConfdRequest.
281 e4ccf6cd Guido Trotter

282 e4ccf6cd Guido Trotter
  This version of the class helps creating requests, on the client side, by
283 e4ccf6cd Guido Trotter
  filling in some default values.
284 e4ccf6cd Guido Trotter

285 e4ccf6cd Guido Trotter
  """
286 e4ccf6cd Guido Trotter
  def __init__(self, **kwargs):
287 e4ccf6cd Guido Trotter
    objects.ConfdRequest.__init__(self, **kwargs)
288 e4ccf6cd Guido Trotter
    if not self.rsalt:
289 e4ccf6cd Guido Trotter
      self.rsalt = utils.NewUUID()
290 e4ccf6cd Guido Trotter
    if not self.protocol:
291 e4ccf6cd Guido Trotter
      self.protocol = constants.CONFD_PROTOCOL_VERSION
292 e4ccf6cd Guido Trotter
    if self.type not in constants.CONFD_REQS:
293 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")
294 e4ccf6cd Guido Trotter
295 392ca296 Guido Trotter
296 392ca296 Guido Trotter
class ConfdFilterCallback:
297 392ca296 Guido Trotter
  """Callback that calls another callback, but filters duplicate results.
298 392ca296 Guido Trotter

299 392ca296 Guido Trotter
  """
300 392ca296 Guido Trotter
  def __init__(self, callback, logger=None):
301 392ca296 Guido Trotter
    """Constructor for ConfdFilterCallback
302 392ca296 Guido Trotter

303 392ca296 Guido Trotter
    @type callback: f(L{ConfdUpcallPayload})
304 392ca296 Guido Trotter
    @param callback: function to call when getting answers
305 392ca296 Guido Trotter
    @type logger: L{logging.Logger}
306 392ca296 Guido Trotter
    @keyword logger: optional logger for internal conditions
307 392ca296 Guido Trotter

308 392ca296 Guido Trotter
    """
309 392ca296 Guido Trotter
    if not callable(callback):
310 392ca296 Guido Trotter
      raise errors.ProgrammerError("callback must be callable")
311 392ca296 Guido Trotter
312 392ca296 Guido Trotter
    self._callback = callback
313 392ca296 Guido Trotter
    self._logger = logger
314 392ca296 Guido Trotter
    # answers contains a dict of salt -> answer
315 392ca296 Guido Trotter
    self._answers = {}
316 392ca296 Guido Trotter
317 392ca296 Guido Trotter
  def _LogFilter(self, salt, new_reply, old_reply):
318 392ca296 Guido Trotter
    if not self._logger:
319 392ca296 Guido Trotter
      return
320 392ca296 Guido Trotter
321 392ca296 Guido Trotter
    if new_reply.serial > old_reply.serial:
322 392ca296 Guido Trotter
      self._logger.debug("Filtering confirming answer, with newer"
323 392ca296 Guido Trotter
                         " serial for query %s" % salt)
324 392ca296 Guido Trotter
    elif new_reply.serial == old_reply.serial:
325 392ca296 Guido Trotter
      if new_reply.answer != old_reply.answer:
326 392ca296 Guido Trotter
        self._logger.warning("Got incoherent answers for query %s"
327 392ca296 Guido Trotter
                             " (serial: %s)" % (salt, new_reply.serial))
328 392ca296 Guido Trotter
      else:
329 392ca296 Guido Trotter
        self._logger.debug("Filtering confirming answer, with same"
330 392ca296 Guido Trotter
                           " serial for query %s" % salt)
331 392ca296 Guido Trotter
    else:
332 392ca296 Guido Trotter
      self._logger.debug("Filtering outdated answer for query %s"
333 392ca296 Guido Trotter
                         " serial: (%d < %d)" % (salt, old_reply.serial,
334 392ca296 Guido Trotter
                                                 new_reply.serial))
335 392ca296 Guido Trotter
336 392ca296 Guido Trotter
  def _HandleExpire(self, up):
337 392ca296 Guido Trotter
    # if we have no answer we have received none, before the expiration.
338 392ca296 Guido Trotter
    if salt in self._answers:
339 392ca296 Guido Trotter
      del self._answers[salt]
340 392ca296 Guido Trotter
341 392ca296 Guido Trotter
  def _HandleReply(self, up):
342 392ca296 Guido Trotter
    """Handle a single confd reply, and decide whether to filter it.
343 392ca296 Guido Trotter

344 392ca296 Guido Trotter
    @rtype: boolean
345 392ca296 Guido Trotter
    @return: True if the reply should be filtered, False if it should be passed
346 392ca296 Guido Trotter
             on to the up-callback
347 392ca296 Guido Trotter

348 392ca296 Guido Trotter
    """
349 392ca296 Guido Trotter
    filter_upcall = False
350 392ca296 Guido Trotter
    salt = up.salt
351 392ca296 Guido Trotter
    if salt not in self._answers:
352 392ca296 Guido Trotter
      # first answer for a query (don't filter, and record)
353 392ca296 Guido Trotter
      self._answers[salt] = up.server_reply
354 392ca296 Guido Trotter
    elif up.server_reply.serial > self._answers[salt].serial:
355 392ca296 Guido Trotter
      # newer answer (record, and compare contents)
356 392ca296 Guido Trotter
      old_answer = self._answers[salt]
357 392ca296 Guido Trotter
      self._answers[salt] = up.server_reply
358 392ca296 Guido Trotter
      if up.server_reply.answer == old_answer.answer:
359 392ca296 Guido Trotter
        # same content (filter) (version upgrade was unrelated)
360 392ca296 Guido Trotter
        filter_upcall = True
361 392ca296 Guido Trotter
        self._LogFilter(salt, up.server_reply, old_answer)
362 392ca296 Guido Trotter
      # else: different content, pass up a second answer
363 392ca296 Guido Trotter
    else:
364 392ca296 Guido Trotter
      # older or same-version answer (duplicate or outdated, filter)
365 392ca296 Guido Trotter
      filter_upcall = True
366 392ca296 Guido Trotter
      self._LogFilter(salt, up.server_reply, self._answers[salt])
367 392ca296 Guido Trotter
368 392ca296 Guido Trotter
    return filter_upcall
369 392ca296 Guido Trotter
370 392ca296 Guido Trotter
  def __call__(self, up):
371 392ca296 Guido Trotter
    """Filtering callback
372 392ca296 Guido Trotter

373 392ca296 Guido Trotter
    @type up: L{ConfdUpcallPayload}
374 392ca296 Guido Trotter
    @param up: upper callback
375 392ca296 Guido Trotter

376 392ca296 Guido Trotter
    """
377 392ca296 Guido Trotter
    filter_upcall = False
378 392ca296 Guido Trotter
    if up.type == UPCALL_REPLY:
379 392ca296 Guido Trotter
      filter_upcall = self._HandleReply(up)
380 392ca296 Guido Trotter
    elif up.type == UPCALL_EXPIRE:
381 392ca296 Guido Trotter
      self._HandleExpire(up)
382 392ca296 Guido Trotter
383 392ca296 Guido Trotter
    if not filter_upcall:
384 392ca296 Guido Trotter
      self._callback(up)