Revision bac6ea51

b/lib/http/client.py
35 35

  
36 36
class HttpClientRequest(object):
37 37
  def __init__(self, host, port, method, path, headers=None, post_data=None,
38
               read_timeout=None, curl_config_fn=None, nicename=None):
38
               read_timeout=None, curl_config_fn=None, nicename=None,
39
               completion_cb=None):
39 40
    """Describes an HTTP request.
40 41

  
41 42
    @type host: string
......
58 59
    @type nicename: string
59 60
    @param nicename: Name, presentable to a user, to describe this request (no
60 61
                     whitespace)
62
    @type completion_cb: callable accepting this request object as a single
63
                         parameter
64
    @param completion_cb: Callback for request completion
61 65

  
62 66
    """
63 67
    assert path.startswith("/"), "Path must start with slash (/)"
64 68
    assert curl_config_fn is None or callable(curl_config_fn)
69
    assert completion_cb is None or callable(completion_cb)
65 70

  
66 71
    # Request attributes
67 72
    self.host = host
......
71 76
    self.read_timeout = read_timeout
72 77
    self.curl_config_fn = curl_config_fn
73 78
    self.nicename = nicename
79
    self.completion_cb = completion_cb
74 80

  
75 81
    if post_data is None:
76 82
      self.post_data = ""
......
220 226
    req.resp_body = self._resp_buffer_read()
221 227

  
222 228
    # Ensure no potentially large variables are referenced
223
    try:
224
      # Only available in PycURL 7.19.0 and above
225
      reset_fn = curl.reset
226
    except AttributeError:
227
      curl.setopt(pycurl.POSTFIELDS, "")
228
      curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
229
    else:
230
      reset_fn()
229
    curl.setopt(pycurl.POSTFIELDS, "")
230
    curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
231

  
232
    if req.completion_cb:
233
      req.completion_cb(req)
231 234

  
232 235

  
233 236
class _NoOpRequestMonitor: # pylint: disable=W0232
b/test/ganeti.http_unittest.py
377 377
    cr = http.client.HttpClientRequest("localhost", 1234, "GET", "/version")
378 378
    self.assertEqual(cr.post_data, "")
379 379

  
380
  def testCompletionCallback(self):
381
    for argname in ["completion_cb", "curl_config_fn"]:
382
      kwargs = {
383
        argname: NotImplementedError,
384
        }
385
      cr = http.client.HttpClientRequest("localhost", 14038, "GET", "/version",
386
                                         **kwargs)
387
      self.assertEqual(getattr(cr, argname), NotImplementedError)
388

  
389
      for fn in [NotImplemented, {}, 1]:
390
        kwargs = {
391
          argname: fn,
392
          }
393
        self.assertRaises(AssertionError, http.client.HttpClientRequest,
394
                          "localhost", 23150, "GET", "/version", **kwargs)
395

  
380 396

  
381 397
class _FakeCurl:
382 398
  def __init__(self):
......
619 635
    def cfg_fn(port, curl):
620 636
      curl.opts["__port__"] = port
621 637

  
622
    def _LockCheckReset(monitor, curl):
638
    def _LockCheckReset(monitor, req):
623 639
      self.assertTrue(monitor._lock.is_owned(shared=0),
624 640
                      msg="Lock must be owned in exclusive mode")
625
      curl.opts["__lockcheck__"] = True
641
      assert not hasattr(req, "lockcheck__")
642
      setattr(req, "lockcheck__", True)
643

  
644
    def _BuildNiceName(port, default=None):
645
      if port % 5 == 0:
646
        return "nicename%s" % port
647
      else:
648
        # Use standard name
649
        return default
626 650

  
627 651
    requests = \
628 652
      [http.client.HttpClientRequest("localhost", i, "POST", "/version%s" % i,
629
                                     curl_config_fn=compat.partial(cfg_fn, i))
653
                                     curl_config_fn=compat.partial(cfg_fn, i),
654
                                     completion_cb=NotImplementedError,
655
                                     nicename=_BuildNiceName(i))
630 656
       for i in range(15176, 15501)]
631 657
    requests_count = len(requests)
632 658

  
......
641 667
      self.assertTrue(compat.all(isinstance(curl, _FakeCurl)
642 668
                                 for curl in handles))
643 669

  
670
      # Prepare for lock check
671
      for req in requests:
672
        assert req.completion_cb is NotImplementedError
673
        if use_monitor:
674
          req.completion_cb = \
675
            compat.partial(_LockCheckReset, lock_monitor_cb.GetMonitor())
676

  
644 677
      for idx, curl in enumerate(handles):
645
        port = curl.opts["__port__"]
678
        try:
679
          port = curl.opts["__port__"]
680
        except KeyError:
681
          self.fail("Per-request config function was not called")
646 682

  
647 683
        if use_monitor:
648 684
          # Check if lock information is correct
649 685
          lock_info = lock_monitor_cb.GetMonitor().GetLockInfo(None)
650 686
          expected = \
651
            [("rpc/localhost/version%s" % handle.opts["__port__"], None,
687
            [("rpc/%s" % (_BuildNiceName(handle.opts["__port__"],
688
                                         default=("localhost/version%s" %
689
                                                  handle.opts["__port__"]))),
690
              None,
652 691
              [threading.currentThread().getName()], None)
653 692
             for handle in handles[idx:]]
654 693
          self.assertEqual(sorted(lock_info), sorted(expected))
......
664 703
          pycurl.RESPONSE_CODE: response_code,
665 704
          }
666 705

  
667
        # Unset options which will be reset
668
        assert not hasattr(curl, "reset")
669
        if use_monitor:
670
          setattr(curl, "reset",
671
                  compat.partial(_LockCheckReset, lock_monitor_cb.GetMonitor(),
672
                                 curl))
673
        else:
674
          self.assertFalse(curl.opts.pop(pycurl.POSTFIELDS))
675
          self.assertTrue(callable(curl.opts.pop(pycurl.WRITEFUNCTION)))
706
        # Prepare for reset
707
        self.assertFalse(curl.opts.pop(pycurl.POSTFIELDS))
708
        self.assertTrue(callable(curl.opts.pop(pycurl.WRITEFUNCTION)))
676 709

  
677 710
        yield (curl, msg)
678 711

  
679 712
      if use_monitor:
680
        self.assertTrue(compat.all(curl.opts["__lockcheck__"]
681
                                   for curl in handles))
713
        self.assertTrue(compat.all(req.lockcheck__ for req in requests))
714

  
715
    if use_monitor:
716
      self.assertEqual(lock_monitor_cb.GetMonitor(), None)
682 717

  
683 718
    http.client.ProcessRequests(requests, lock_monitor_cb=lock_monitor_cb,
684 719
                                _curl=_FakeCurl,

Also available in: Unified diff