Statistics
| Branch: | Tag: | Revision:

root / lib / confd / client.py @ 6c881c52

History | View | Annotate | Download (12.9 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 69b99987 Michael Hanselmann
Example usage::
33 69b99987 Michael Hanselmann

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

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

46 e4ccf6cd Guido Trotter
"""
47 69b99987 Michael Hanselmann
48 6c881c52 Iustin Pop
# pylint: disable-msg=E0203
49 6c881c52 Iustin Pop
50 6c881c52 Iustin Pop
# E0203: Access to member %r before its definition, since we use
51 6c881c52 Iustin Pop
# objects.py which doesn't explicitely initialise its members
52 6c881c52 Iustin Pop
53 e4ccf6cd Guido Trotter
import socket
54 e4ccf6cd Guido Trotter
import time
55 e4ccf6cd Guido Trotter
import random
56 e4ccf6cd Guido Trotter
57 e4ccf6cd Guido Trotter
from ganeti import utils
58 e4ccf6cd Guido Trotter
from ganeti import constants
59 e4ccf6cd Guido Trotter
from ganeti import objects
60 e4ccf6cd Guido Trotter
from ganeti import serializer
61 e4ccf6cd Guido Trotter
from ganeti import daemon # contains AsyncUDPSocket
62 e4ccf6cd Guido Trotter
from ganeti import errors
63 e4ccf6cd Guido Trotter
from ganeti import confd
64 e4ccf6cd Guido Trotter
65 e4ccf6cd Guido Trotter
66 e4ccf6cd Guido Trotter
class ConfdAsyncUDPClient(daemon.AsyncUDPSocket):
67 e4ccf6cd Guido Trotter
  """Confd udp asyncore client
68 e4ccf6cd Guido Trotter

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

72 e4ccf6cd Guido Trotter
  """
73 e4ccf6cd Guido Trotter
  def __init__(self, client):
74 e4ccf6cd Guido Trotter
    """Constructor for ConfdAsyncUDPClient
75 e4ccf6cd Guido Trotter

76 e4ccf6cd Guido Trotter
    @type client: L{ConfdClient}
77 e4ccf6cd Guido Trotter
    @param client: client library, to pass the datagrams to
78 e4ccf6cd Guido Trotter

79 e4ccf6cd Guido Trotter
    """
80 e4ccf6cd Guido Trotter
    daemon.AsyncUDPSocket.__init__(self)
81 e4ccf6cd Guido Trotter
    self.client = client
82 e4ccf6cd Guido Trotter
83 e4ccf6cd Guido Trotter
  # this method is overriding a daemon.AsyncUDPSocket method
84 e4ccf6cd Guido Trotter
  def handle_datagram(self, payload, ip, port):
85 e4ccf6cd Guido Trotter
    self.client.HandleResponse(payload, ip, port)
86 e4ccf6cd Guido Trotter
87 e4ccf6cd Guido Trotter
88 e4ccf6cd Guido Trotter
class ConfdClient:
89 e4ccf6cd Guido Trotter
  """Send queries to confd, and get back answers.
90 e4ccf6cd Guido Trotter

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

95 e4ccf6cd Guido Trotter
  """
96 a3db74e4 Guido Trotter
  def __init__(self, hmac_key, peers, callback, port=None, logger=None):
97 e4ccf6cd Guido Trotter
    """Constructor for ConfdClient
98 e4ccf6cd Guido Trotter

99 e4ccf6cd Guido Trotter
    @type hmac_key: string
100 e4ccf6cd Guido Trotter
    @param hmac_key: hmac key to talk to confd
101 e4ccf6cd Guido Trotter
    @type peers: list
102 e4ccf6cd Guido Trotter
    @param peers: list of peer nodes
103 96e03b0b Guido Trotter
    @type callback: f(L{ConfdUpcallPayload})
104 96e03b0b Guido Trotter
    @param callback: function to call when getting answers
105 7d20c647 Guido Trotter
    @type port: integer
106 7d20c647 Guido Trotter
    @keyword port: confd port (default: use GetDaemonPort)
107 69b99987 Michael Hanselmann
    @type logger: logging.Logger
108 a3db74e4 Guido Trotter
    @keyword logger: optional logger for internal conditions
109 e4ccf6cd Guido Trotter

110 e4ccf6cd Guido Trotter
    """
111 96e03b0b Guido Trotter
    if not callable(callback):
112 96e03b0b Guido Trotter
      raise errors.ProgrammerError("callback must be callable")
113 e4ccf6cd Guido Trotter
114 a5229439 Guido Trotter
    self.UpdatePeerList(peers)
115 e4ccf6cd Guido Trotter
    self._hmac_key = hmac_key
116 e4ccf6cd Guido Trotter
    self._socket = ConfdAsyncUDPClient(self)
117 96e03b0b Guido Trotter
    self._callback = callback
118 7d20c647 Guido Trotter
    self._confd_port = port
119 a3db74e4 Guido Trotter
    self._logger = logger
120 96e03b0b Guido Trotter
    self._requests = {}
121 96e03b0b Guido Trotter
    self._expire_requests = []
122 7d20c647 Guido Trotter
123 7d20c647 Guido Trotter
    if self._confd_port is None:
124 7d20c647 Guido Trotter
      self._confd_port = utils.GetDaemonPort(constants.CONFD)
125 e4ccf6cd Guido Trotter
126 a5229439 Guido Trotter
  def UpdatePeerList(self, peers):
127 a5229439 Guido Trotter
    """Update the list of peers
128 a5229439 Guido Trotter

129 a5229439 Guido Trotter
    @type peers: list
130 a5229439 Guido Trotter
    @param peers: list of peer nodes
131 a5229439 Guido Trotter

132 a5229439 Guido Trotter
    """
133 a5229439 Guido Trotter
    if not isinstance(peers, list):
134 a5229439 Guido Trotter
      raise errors.ProgrammerError("peers must be a list")
135 a5229439 Guido Trotter
    self._peers = peers
136 a5229439 Guido Trotter
137 e4ccf6cd Guido Trotter
  def _PackRequest(self, request, now=None):
138 e4ccf6cd Guido Trotter
    """Prepare a request to be sent on the wire.
139 e4ccf6cd Guido Trotter

140 e4ccf6cd Guido Trotter
    This function puts a proper salt in a confd request, puts the proper salt,
141 e4ccf6cd Guido Trotter
    and adds the correct magic number.
142 e4ccf6cd Guido Trotter

143 e4ccf6cd Guido Trotter
    """
144 e4ccf6cd Guido Trotter
    if now is None:
145 e4ccf6cd Guido Trotter
      now = time.time()
146 e4ccf6cd Guido Trotter
    tstamp = '%d' % now
147 e4ccf6cd Guido Trotter
    req = serializer.DumpSignedJson(request.ToDict(), self._hmac_key, tstamp)
148 e4ccf6cd Guido Trotter
    return confd.PackMagic(req)
149 e4ccf6cd Guido Trotter
150 e4ccf6cd Guido Trotter
  def _UnpackReply(self, payload):
151 e4ccf6cd Guido Trotter
    in_payload = confd.UnpackMagic(payload)
152 c103d7ae Guido Trotter
    (dict_answer, salt) = serializer.LoadSignedJson(in_payload, self._hmac_key)
153 c103d7ae Guido Trotter
    answer = objects.ConfdReply.FromDict(dict_answer)
154 e4ccf6cd Guido Trotter
    return answer, salt
155 e4ccf6cd Guido Trotter
156 96e03b0b Guido Trotter
  def ExpireRequests(self):
157 96e03b0b Guido Trotter
    """Delete all the expired requests.
158 e4ccf6cd Guido Trotter

159 e4ccf6cd Guido Trotter
    """
160 e4ccf6cd Guido Trotter
    now = time.time()
161 96e03b0b Guido Trotter
    while self._expire_requests:
162 96e03b0b Guido Trotter
      expire_time, rsalt = self._expire_requests[0]
163 e4ccf6cd Guido Trotter
      if now >= expire_time:
164 96e03b0b Guido Trotter
        self._expire_requests.pop(0)
165 96e03b0b Guido Trotter
        (request, args) = self._requests[rsalt]
166 96e03b0b Guido Trotter
        del self._requests[rsalt]
167 96e03b0b Guido Trotter
        client_reply = ConfdUpcallPayload(salt=rsalt,
168 96e03b0b Guido Trotter
                                          type=UPCALL_EXPIRE,
169 96e03b0b Guido Trotter
                                          orig_request=request,
170 5f6f260a Guido Trotter
                                          extra_args=args,
171 5f6f260a Guido Trotter
                                          client=self,
172 5f6f260a Guido Trotter
                                          )
173 96e03b0b Guido Trotter
        self._callback(client_reply)
174 e4ccf6cd Guido Trotter
      else:
175 e4ccf6cd Guido Trotter
        break
176 e4ccf6cd Guido Trotter
177 96e03b0b Guido Trotter
  def SendRequest(self, request, args=None, coverage=None):
178 e4ccf6cd Guido Trotter
    """Send a confd request to some MCs
179 e4ccf6cd Guido Trotter

180 e4ccf6cd Guido Trotter
    @type request: L{objects.ConfdRequest}
181 e4ccf6cd Guido Trotter
    @param request: the request to send
182 e4ccf6cd Guido Trotter
    @type args: tuple
183 90469357 Guido Trotter
    @keyword args: additional callback arguments
184 e4ccf6cd Guido Trotter
    @type coverage: integer
185 e4ccf6cd Guido Trotter
    @keyword coverage: number of remote nodes to contact
186 e4ccf6cd Guido Trotter

187 e4ccf6cd Guido Trotter
    """
188 e4ccf6cd Guido Trotter
    if coverage is None:
189 e4ccf6cd Guido Trotter
      coverage = min(len(self._peers), constants.CONFD_DEFAULT_REQ_COVERAGE)
190 e4ccf6cd Guido Trotter
191 e4ccf6cd Guido Trotter
    if coverage > len(self._peers):
192 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Not enough MCs known to provide the"
193 e4ccf6cd Guido Trotter
                                    " desired coverage")
194 e4ccf6cd Guido Trotter
195 e4ccf6cd Guido Trotter
    if not request.rsalt:
196 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Missing request rsalt")
197 e4ccf6cd Guido Trotter
198 96e03b0b Guido Trotter
    self.ExpireRequests()
199 96e03b0b Guido Trotter
    if request.rsalt in self._requests:
200 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Duplicate request rsalt")
201 e4ccf6cd Guido Trotter
202 e4ccf6cd Guido Trotter
    if request.type not in constants.CONFD_REQS:
203 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")
204 e4ccf6cd Guido Trotter
205 e4ccf6cd Guido Trotter
    random.shuffle(self._peers)
206 e4ccf6cd Guido Trotter
    targets = self._peers[:coverage]
207 e4ccf6cd Guido Trotter
208 e4ccf6cd Guido Trotter
    now = time.time()
209 e4ccf6cd Guido Trotter
    payload = self._PackRequest(request, now=now)
210 e4ccf6cd Guido Trotter
211 e4ccf6cd Guido Trotter
    for target in targets:
212 e4ccf6cd Guido Trotter
      try:
213 e4ccf6cd Guido Trotter
        self._socket.enqueue_send(target, self._confd_port, payload)
214 e4ccf6cd Guido Trotter
      except errors.UdpDataSizeError:
215 e4ccf6cd Guido Trotter
        raise errors.ConfdClientError("Request too big")
216 e4ccf6cd Guido Trotter
217 96e03b0b Guido Trotter
    self._requests[request.rsalt] = (request, args)
218 e4ccf6cd Guido Trotter
    expire_time = now + constants.CONFD_CLIENT_EXPIRE_TIMEOUT
219 96e03b0b Guido Trotter
    self._expire_requests.append((expire_time, request.rsalt))
220 e4ccf6cd Guido Trotter
221 e4ccf6cd Guido Trotter
  def HandleResponse(self, payload, ip, port):
222 e4ccf6cd Guido Trotter
    """Asynchronous handler for a confd reply
223 e4ccf6cd Guido Trotter

224 e4ccf6cd Guido Trotter
    Call the relevant callback associated to the current request.
225 e4ccf6cd Guido Trotter

226 e4ccf6cd Guido Trotter
    """
227 e4ccf6cd Guido Trotter
    try:
228 e4ccf6cd Guido Trotter
      try:
229 e4ccf6cd Guido Trotter
        answer, salt = self._UnpackReply(payload)
230 a3db74e4 Guido Trotter
      except (errors.SignatureError, errors.ConfdMagicError), err:
231 a3db74e4 Guido Trotter
        if self._logger:
232 a3db74e4 Guido Trotter
          self._logger.debug("Discarding broken package: %s" % err)
233 e4ccf6cd Guido Trotter
        return
234 e4ccf6cd Guido Trotter
235 e4ccf6cd Guido Trotter
      try:
236 96e03b0b Guido Trotter
        (request, args) = self._requests[salt]
237 e4ccf6cd Guido Trotter
      except KeyError:
238 a3db74e4 Guido Trotter
        if self._logger:
239 a3db74e4 Guido Trotter
          self._logger.debug("Discarding unknown (expired?) reply: %s" % err)
240 96e03b0b Guido Trotter
        return
241 96e03b0b Guido Trotter
242 96e03b0b Guido Trotter
      client_reply = ConfdUpcallPayload(salt=salt,
243 96e03b0b Guido Trotter
                                        type=UPCALL_REPLY,
244 96e03b0b Guido Trotter
                                        server_reply=answer,
245 96e03b0b Guido Trotter
                                        orig_request=request,
246 96e03b0b Guido Trotter
                                        server_ip=ip,
247 96e03b0b Guido Trotter
                                        server_port=port,
248 5f6f260a Guido Trotter
                                        extra_args=args,
249 5f6f260a Guido Trotter
                                        client=self,
250 5f6f260a Guido Trotter
                                       )
251 96e03b0b Guido Trotter
      self._callback(client_reply)
252 e4ccf6cd Guido Trotter
253 e4ccf6cd Guido Trotter
    finally:
254 96e03b0b Guido Trotter
      self.ExpireRequests()
255 96e03b0b Guido Trotter
256 96e03b0b Guido Trotter
257 96e03b0b Guido Trotter
# UPCALL_REPLY: server reply upcall
258 96e03b0b Guido Trotter
# has all ConfdUpcallPayload fields populated
259 96e03b0b Guido Trotter
UPCALL_REPLY = 1
260 96e03b0b Guido Trotter
# UPCALL_EXPIRE: internal library request expire
261 96e03b0b Guido Trotter
# has only salt, type, orig_request and extra_args
262 96e03b0b Guido Trotter
UPCALL_EXPIRE = 2
263 96e03b0b Guido Trotter
CONFD_UPCALL_TYPES = frozenset([
264 96e03b0b Guido Trotter
  UPCALL_REPLY,
265 96e03b0b Guido Trotter
  UPCALL_EXPIRE,
266 96e03b0b Guido Trotter
  ])
267 96e03b0b Guido Trotter
268 96e03b0b Guido Trotter
269 96e03b0b Guido Trotter
class ConfdUpcallPayload(objects.ConfigObject):
270 96e03b0b Guido Trotter
  """Callback argument for confd replies
271 96e03b0b Guido Trotter

272 96e03b0b Guido Trotter
  @type salt: string
273 96e03b0b Guido Trotter
  @ivar salt: salt associated with the query
274 96e03b0b Guido Trotter
  @type type: one of confd.client.CONFD_UPCALL_TYPES
275 96e03b0b Guido Trotter
  @ivar type: upcall type (server reply, expired request, ...)
276 96e03b0b Guido Trotter
  @type orig_request: L{objects.ConfdRequest}
277 96e03b0b Guido Trotter
  @ivar orig_request: original request
278 96e03b0b Guido Trotter
  @type server_reply: L{objects.ConfdReply}
279 96e03b0b Guido Trotter
  @ivar server_reply: server reply
280 96e03b0b Guido Trotter
  @type server_ip: string
281 96e03b0b Guido Trotter
  @ivar server_ip: answering server ip address
282 96e03b0b Guido Trotter
  @type server_port: int
283 96e03b0b Guido Trotter
  @ivar server_port: answering server port
284 96e03b0b Guido Trotter
  @type extra_args: any
285 96e03b0b Guido Trotter
  @ivar extra_args: 'args' argument of the SendRequest function
286 5f6f260a Guido Trotter
  @type client: L{ConfdClient}
287 5f6f260a Guido Trotter
  @ivar client: current confd client instance
288 96e03b0b Guido Trotter

289 96e03b0b Guido Trotter
  """
290 96e03b0b Guido Trotter
  __slots__ = [
291 96e03b0b Guido Trotter
    "salt",
292 96e03b0b Guido Trotter
    "type",
293 96e03b0b Guido Trotter
    "orig_request",
294 96e03b0b Guido Trotter
    "server_reply",
295 96e03b0b Guido Trotter
    "server_ip",
296 96e03b0b Guido Trotter
    "server_port",
297 96e03b0b Guido Trotter
    "extra_args",
298 5f6f260a Guido Trotter
    "client",
299 96e03b0b Guido Trotter
    ]
300 e4ccf6cd Guido Trotter
301 e4ccf6cd Guido Trotter
302 e4ccf6cd Guido Trotter
class ConfdClientRequest(objects.ConfdRequest):
303 e4ccf6cd Guido Trotter
  """This is the client-side version of ConfdRequest.
304 e4ccf6cd Guido Trotter

305 e4ccf6cd Guido Trotter
  This version of the class helps creating requests, on the client side, by
306 e4ccf6cd Guido Trotter
  filling in some default values.
307 e4ccf6cd Guido Trotter

308 e4ccf6cd Guido Trotter
  """
309 e4ccf6cd Guido Trotter
  def __init__(self, **kwargs):
310 e4ccf6cd Guido Trotter
    objects.ConfdRequest.__init__(self, **kwargs)
311 e4ccf6cd Guido Trotter
    if not self.rsalt:
312 e4ccf6cd Guido Trotter
      self.rsalt = utils.NewUUID()
313 e4ccf6cd Guido Trotter
    if not self.protocol:
314 e4ccf6cd Guido Trotter
      self.protocol = constants.CONFD_PROTOCOL_VERSION
315 e4ccf6cd Guido Trotter
    if self.type not in constants.CONFD_REQS:
316 e4ccf6cd Guido Trotter
      raise errors.ConfdClientError("Invalid request type")
317 e4ccf6cd Guido Trotter
318 392ca296 Guido Trotter
319 392ca296 Guido Trotter
class ConfdFilterCallback:
320 392ca296 Guido Trotter
  """Callback that calls another callback, but filters duplicate results.
321 392ca296 Guido Trotter

322 392ca296 Guido Trotter
  """
323 392ca296 Guido Trotter
  def __init__(self, callback, logger=None):
324 392ca296 Guido Trotter
    """Constructor for ConfdFilterCallback
325 392ca296 Guido Trotter

326 392ca296 Guido Trotter
    @type callback: f(L{ConfdUpcallPayload})
327 392ca296 Guido Trotter
    @param callback: function to call when getting answers
328 69b99987 Michael Hanselmann
    @type logger: logging.Logger
329 392ca296 Guido Trotter
    @keyword logger: optional logger for internal conditions
330 392ca296 Guido Trotter

331 392ca296 Guido Trotter
    """
332 392ca296 Guido Trotter
    if not callable(callback):
333 392ca296 Guido Trotter
      raise errors.ProgrammerError("callback must be callable")
334 392ca296 Guido Trotter
335 392ca296 Guido Trotter
    self._callback = callback
336 392ca296 Guido Trotter
    self._logger = logger
337 392ca296 Guido Trotter
    # answers contains a dict of salt -> answer
338 392ca296 Guido Trotter
    self._answers = {}
339 392ca296 Guido Trotter
340 392ca296 Guido Trotter
  def _LogFilter(self, salt, new_reply, old_reply):
341 392ca296 Guido Trotter
    if not self._logger:
342 392ca296 Guido Trotter
      return
343 392ca296 Guido Trotter
344 392ca296 Guido Trotter
    if new_reply.serial > old_reply.serial:
345 392ca296 Guido Trotter
      self._logger.debug("Filtering confirming answer, with newer"
346 392ca296 Guido Trotter
                         " serial for query %s" % salt)
347 392ca296 Guido Trotter
    elif new_reply.serial == old_reply.serial:
348 392ca296 Guido Trotter
      if new_reply.answer != old_reply.answer:
349 392ca296 Guido Trotter
        self._logger.warning("Got incoherent answers for query %s"
350 392ca296 Guido Trotter
                             " (serial: %s)" % (salt, new_reply.serial))
351 392ca296 Guido Trotter
      else:
352 392ca296 Guido Trotter
        self._logger.debug("Filtering confirming answer, with same"
353 392ca296 Guido Trotter
                           " serial for query %s" % salt)
354 392ca296 Guido Trotter
    else:
355 392ca296 Guido Trotter
      self._logger.debug("Filtering outdated answer for query %s"
356 392ca296 Guido Trotter
                         " serial: (%d < %d)" % (salt, old_reply.serial,
357 392ca296 Guido Trotter
                                                 new_reply.serial))
358 392ca296 Guido Trotter
359 392ca296 Guido Trotter
  def _HandleExpire(self, up):
360 392ca296 Guido Trotter
    # if we have no answer we have received none, before the expiration.
361 a9613def Guido Trotter
    if up.salt in self._answers:
362 a9613def Guido Trotter
      del self._answers[up.salt]
363 392ca296 Guido Trotter
364 392ca296 Guido Trotter
  def _HandleReply(self, up):
365 392ca296 Guido Trotter
    """Handle a single confd reply, and decide whether to filter it.
366 392ca296 Guido Trotter

367 392ca296 Guido Trotter
    @rtype: boolean
368 392ca296 Guido Trotter
    @return: True if the reply should be filtered, False if it should be passed
369 392ca296 Guido Trotter
             on to the up-callback
370 392ca296 Guido Trotter

371 392ca296 Guido Trotter
    """
372 392ca296 Guido Trotter
    filter_upcall = False
373 392ca296 Guido Trotter
    salt = up.salt
374 392ca296 Guido Trotter
    if salt not in self._answers:
375 392ca296 Guido Trotter
      # first answer for a query (don't filter, and record)
376 392ca296 Guido Trotter
      self._answers[salt] = up.server_reply
377 392ca296 Guido Trotter
    elif up.server_reply.serial > self._answers[salt].serial:
378 392ca296 Guido Trotter
      # newer answer (record, and compare contents)
379 392ca296 Guido Trotter
      old_answer = self._answers[salt]
380 392ca296 Guido Trotter
      self._answers[salt] = up.server_reply
381 392ca296 Guido Trotter
      if up.server_reply.answer == old_answer.answer:
382 392ca296 Guido Trotter
        # same content (filter) (version upgrade was unrelated)
383 392ca296 Guido Trotter
        filter_upcall = True
384 392ca296 Guido Trotter
        self._LogFilter(salt, up.server_reply, old_answer)
385 392ca296 Guido Trotter
      # else: different content, pass up a second answer
386 392ca296 Guido Trotter
    else:
387 392ca296 Guido Trotter
      # older or same-version answer (duplicate or outdated, filter)
388 392ca296 Guido Trotter
      filter_upcall = True
389 392ca296 Guido Trotter
      self._LogFilter(salt, up.server_reply, self._answers[salt])
390 392ca296 Guido Trotter
391 392ca296 Guido Trotter
    return filter_upcall
392 392ca296 Guido Trotter
393 392ca296 Guido Trotter
  def __call__(self, up):
394 392ca296 Guido Trotter
    """Filtering callback
395 392ca296 Guido Trotter

396 392ca296 Guido Trotter
    @type up: L{ConfdUpcallPayload}
397 392ca296 Guido Trotter
    @param up: upper callback
398 392ca296 Guido Trotter

399 392ca296 Guido Trotter
    """
400 392ca296 Guido Trotter
    filter_upcall = False
401 392ca296 Guido Trotter
    if up.type == UPCALL_REPLY:
402 392ca296 Guido Trotter
      filter_upcall = self._HandleReply(up)
403 392ca296 Guido Trotter
    elif up.type == UPCALL_EXPIRE:
404 392ca296 Guido Trotter
      self._HandleExpire(up)
405 392ca296 Guido Trotter
406 392ca296 Guido Trotter
    if not filter_upcall:
407 392ca296 Guido Trotter
      self._callback(up)