Revision abbf2cd9 test/ganeti.http_unittest.py

b/test/ganeti.http_unittest.py
26 26
import unittest
27 27
import time
28 28
import tempfile
29
import pycurl
30
import itertools
31
import threading
29 32
from cStringIO import StringIO
30 33

  
31 34
from ganeti import http
35
from ganeti import compat
32 36

  
33 37
import ganeti.http.server
34 38
import ganeti.http.client
......
330 334
    self.assertEqual(cr.headers, [])
331 335
    self.assertEqual(cr.url, "https://localhost:1234/version")
332 336

  
337
  def testPlainAddressIPv4(self):
338
    cr = http.client.HttpClientRequest("192.0.2.9", 19956, "GET", "/version")
339
    self.assertEqual(cr.url, "https://192.0.2.9:19956/version")
340

  
341
  def testPlainAddressIPv6(self):
342
    cr = http.client.HttpClientRequest("2001:db8::cafe", 15110, "GET", "/info")
343
    self.assertEqual(cr.url, "https://[2001:db8::cafe]:15110/info")
344

  
333 345
  def testOldStyleHeaders(self):
334 346
    headers = {
335 347
      "Content-type": "text/plain",
......
365 377
    cr = http.client.HttpClientRequest("localhost", 1234, "GET", "/version")
366 378
    self.assertEqual(cr.post_data, "")
367 379

  
368
  def testIdentity(self):
369
    # These should all use different connections, hence also have a different
370
    # identity
371
    cr1 = http.client.HttpClientRequest("localhost", 1234, "GET", "/version")
372
    cr2 = http.client.HttpClientRequest("localhost", 9999, "GET", "/version")
373
    cr3 = http.client.HttpClientRequest("node1", 1234, "GET", "/version")
374
    cr4 = http.client.HttpClientRequest("node1", 9999, "GET", "/version")
375 380

  
376
    self.assertEqual(len(set([cr1.identity, cr2.identity,
377
                              cr3.identity, cr4.identity])), 4)
381
class _FakeCurl:
382
  def __init__(self):
383
    self.opts = {}
384
    self.info = NotImplemented
385

  
386
  def setopt(self, opt, value):
387
    assert opt not in self.opts, "Option set more than once"
388
    self.opts[opt] = value
389

  
390
  def getinfo(self, info):
391
    return self.info.pop(info)
392

  
393

  
394
class TestClientStartRequest(unittest.TestCase):
395
  @staticmethod
396
  def _TestCurlConfig(curl):
397
    curl.setopt(pycurl.SSLKEYTYPE, "PEM")
398

  
399
  def test(self):
400
    for method in [http.HTTP_GET, http.HTTP_PUT, "CUSTOM"]:
401
      for port in [8761, 29796, 19528]:
402
        for curl_config_fn in [None, self._TestCurlConfig]:
403
          for read_timeout in [None, 0, 1, 123, 36000]:
404
            self._TestInner(method, port, curl_config_fn, read_timeout)
405

  
406
  def _TestInner(self, method, port, curl_config_fn, read_timeout):
407
    for response_code in [http.HTTP_OK, http.HttpNotFound.code,
408
                          http.HTTP_NOT_MODIFIED]:
409
      for response_body in [None, "Hello World",
410
                            "Very Long\tContent here\n" * 171]:
411
        for errmsg in [None, "error"]:
412
          req = http.client.HttpClientRequest("localhost", port, method,
413
                                              "/version",
414
                                              curl_config_fn=curl_config_fn,
415
                                              read_timeout=read_timeout)
416
          curl = _FakeCurl()
417
          pending = http.client._StartRequest(curl, req)
418
          self.assertEqual(pending.GetCurlHandle(), curl)
419
          self.assertEqual(pending.GetCurrentRequest(), req)
420

  
421
          # Check options
422
          opts = curl.opts
423
          self.assertEqual(opts.pop(pycurl.CUSTOMREQUEST), method)
424
          self.assertEqual(opts.pop(pycurl.URL),
425
                           "https://localhost:%s/version" % port)
426
          if read_timeout is None:
427
            self.assertEqual(opts.pop(pycurl.TIMEOUT), 0)
428
          else:
429
            self.assertEqual(opts.pop(pycurl.TIMEOUT), read_timeout)
430
          self.assertFalse(opts.pop(pycurl.VERBOSE))
431
          self.assertTrue(opts.pop(pycurl.NOSIGNAL))
432
          self.assertEqual(opts.pop(pycurl.USERAGENT),
433
                           http.HTTP_GANETI_VERSION)
434
          self.assertEqual(opts.pop(pycurl.PROXY), "")
435
          self.assertFalse(opts.pop(pycurl.POSTFIELDS))
436
          self.assertFalse(opts.pop(pycurl.HTTPHEADER))
437
          write_fn = opts.pop(pycurl.WRITEFUNCTION)
438
          self.assertTrue(callable(write_fn))
439
          if hasattr(pycurl, "SSL_SESSIONID_CACHE"):
440
            self.assertFalse(opts.pop(pycurl.SSL_SESSIONID_CACHE))
441
          if curl_config_fn:
442
            self.assertEqual(opts.pop(pycurl.SSLKEYTYPE), "PEM")
443
          else:
444
            self.assertFalse(pycurl.SSLKEYTYPE in opts)
445
          self.assertFalse(opts)
446

  
447
          if response_body is not None:
448
            offset = 0
449
            while offset < len(response_body):
450
              piece = response_body[offset:offset + 10]
451
              write_fn(piece)
452
              offset += len(piece)
453

  
454
          curl.info = {
455
            pycurl.RESPONSE_CODE: response_code,
456
            }
457

  
458
          # Finalize request
459
          pending.Done(errmsg)
460

  
461
          self.assertFalse(curl.info)
462

  
463
          # Can only finalize once
464
          self.assertRaises(AssertionError, pending.Done, True)
465

  
466
          if errmsg:
467
            self.assertFalse(req.success)
468
          else:
469
            self.assertTrue(req.success)
470
          self.assertEqual(req.error, errmsg)
471
          self.assertEqual(req.resp_status_code, response_code)
472
          if response_body is None:
473
            self.assertEqual(req.resp_body, "")
474
          else:
475
            self.assertEqual(req.resp_body, response_body)
476

  
477
          # Check if resetting worked
478
          assert not hasattr(curl, "reset")
479
          opts = curl.opts
480
          self.assertFalse(opts.pop(pycurl.POSTFIELDS))
481
          self.assertTrue(callable(opts.pop(pycurl.WRITEFUNCTION)))
482
          self.assertFalse(opts)
483

  
484
          self.assertFalse(curl.opts,
485
                           msg="Previous checks did not consume all options")
486
          assert id(opts) == id(curl.opts)
378 487

  
379
    # But this one should have the same
380
    cr1vglist = http.client.HttpClientRequest("localhost", 1234,
381
                                              "GET", "/vg_list")
382
    self.assertEqual(cr1.identity, cr1vglist.identity)
488
  def _TestWrongTypes(self, *args, **kwargs):
489
    req = http.client.HttpClientRequest(*args, **kwargs)
490
    self.assertRaises(AssertionError, http.client._StartRequest,
491
                      _FakeCurl(), req)
383 492

  
493
  def testWrongHostType(self):
494
    self._TestWrongTypes(unicode("localhost"), 8080, "GET", "/version")
495

  
496
  def testWrongUrlType(self):
497
    self._TestWrongTypes("localhost", 8080, "GET", unicode("/version"))
498

  
499
  def testWrongMethodType(self):
500
    self._TestWrongTypes("localhost", 8080, unicode("GET"), "/version")
501

  
502
  def testWrongHeaderType(self):
503
    self._TestWrongTypes("localhost", 8080, "GET", "/version",
504
                         headers={
505
                           unicode("foo"): "bar",
506
                           })
507

  
508
  def testWrongPostDataType(self):
509
    self._TestWrongTypes("localhost", 8080, "GET", "/version",
510
                         post_data=unicode("verylongdata" * 100))
511

  
512

  
513
class _EmptyCurlMulti:
514
  def perform(self):
515
    return (pycurl.E_MULTI_OK, 0)
516

  
517
  def info_read(self):
518
    return (0, [], [])
519

  
520

  
521
class TestClientProcessRequests(unittest.TestCase):
522
  def testEmpty(self):
523
    requests = []
524
    http.client.ProcessRequests(requests, _curl=NotImplemented,
525
                                _curl_multi=_EmptyCurlMulti)
526
    self.assertEqual(requests, [])
527

  
528

  
529
class TestProcessCurlRequests(unittest.TestCase):
530
  class _FakeCurlMulti:
531
    def __init__(self):
532
      self.handles = []
533
      self.will_fail = []
534
      self._expect = ["perform"]
535
      self._counter = itertools.count()
536

  
537
    def add_handle(self, curl):
538
      assert curl not in self.handles
539
      self.handles.append(curl)
540
      if self._counter.next() % 3 == 0:
541
        self.will_fail.append(curl)
542

  
543
    def remove_handle(self, curl):
544
      self.handles.remove(curl)
545

  
546
    def perform(self):
547
      assert self._expect.pop(0) == "perform"
548

  
549
      if self._counter.next() % 2 == 0:
550
        self._expect.append("perform")
551
        return (pycurl.E_CALL_MULTI_PERFORM, None)
552

  
553
      self._expect.append("info_read")
554

  
555
      return (pycurl.E_MULTI_OK, len(self.handles))
556

  
557
    def info_read(self):
558
      assert self._expect.pop(0) == "info_read"
559
      successful = []
560
      failed = []
561
      if self.handles:
562
        if self._counter.next() % 17 == 0:
563
          curl = self.handles[0]
564
          if curl in self.will_fail:
565
            failed.append((curl, -1, "test error"))
566
          else:
567
            successful.append(curl)
568
        remaining_messages = len(self.handles) % 3
569
        if remaining_messages > 0:
570
          self._expect.append("info_read")
571
        else:
572
          self._expect.append("select")
573
      else:
574
        remaining_messages = 0
575
        self._expect.append("select")
576
      return (remaining_messages, successful, failed)
577

  
578
    def select(self, timeout):
579
      # Never compare floats for equality
580
      assert timeout >= 0.95 and timeout <= 1.05
581
      assert self._expect.pop(0) == "select"
582
      self._expect.append("perform")
384 583

  
385
class TestClient(unittest.TestCase):
386 584
  def test(self):
387
    pool = http.client.HttpClientPool(None)
388
    self.assertFalse(pool._pool)
585
    requests = [_FakeCurl() for _ in range(10)]
586
    multi = self._FakeCurlMulti()
587
    for (curl, errmsg) in http.client._ProcessCurlRequests(multi, requests):
588
      self.assertTrue(curl not in multi.handles)
589
      if curl in multi.will_fail:
590
        self.assertTrue("test error" in errmsg)
591
      else:
592
        self.assertTrue(errmsg is None)
593
    self.assertFalse(multi.handles)
594
    self.assertEqual(multi._expect, ["select"])
595

  
596

  
597
class TestProcessRequests(unittest.TestCase):
598
  class _DummyCurlMulti:
599
    pass
600

  
601
  def testNoMonitor(self):
602
    self._Test(False)
603

  
604
  def testWithMonitor(self):
605
    self._Test(True)
606

  
607
  class _MonitorChecker:
608
    def __init__(self):
609
      self._monitor = None
610

  
611
    def GetMonitor(self):
612
      return self._monitor
613

  
614
    def __call__(self, monitor):
615
      assert callable(monitor.GetLockInfo)
616
      self._monitor = monitor
617

  
618
  def _Test(self, use_monitor):
619
    def cfg_fn(port, curl):
620
      curl.opts["__port__"] = port
621

  
622
    def _LockCheckReset(monitor, curl):
623
      self.assertTrue(monitor._lock.is_owned(shared=0),
624
                      msg="Lock must be owned in exclusive mode")
625
      curl.opts["__lockcheck__"] = True
626

  
627
    requests = \
628
      [http.client.HttpClientRequest("localhost", i, "POST", "/version%s" % i,
629
                                     curl_config_fn=compat.partial(cfg_fn, i))
630
       for i in range(15176, 15501)]
631
    requests_count = len(requests)
632

  
633
    if use_monitor:
634
      lock_monitor_cb = self._MonitorChecker()
635
    else:
636
      lock_monitor_cb = None
637

  
638
    def _ProcessRequests(multi, handles):
639
      self.assertTrue(isinstance(multi, self._DummyCurlMulti))
640
      self.assertEqual(len(requests), len(handles))
641
      self.assertTrue(compat.all(isinstance(curl, _FakeCurl)
642
                                 for curl in handles))
643

  
644
      for idx, curl in enumerate(handles):
645
        port = curl.opts["__port__"]
646

  
647
        if use_monitor:
648
          # Check if lock information is correct
649
          lock_info = lock_monitor_cb.GetMonitor().GetLockInfo(None)
650
          expected = \
651
            [("rpc/localhost/version%s" % handle.opts["__port__"], None,
652
              [threading.currentThread().getName()], None)
653
             for handle in handles[idx:]]
654
          self.assertEqual(sorted(lock_info), sorted(expected))
655

  
656
        if port % 3 == 0:
657
          response_code = http.HTTP_OK
658
          msg = None
659
        else:
660
          response_code = http.HttpNotFound.code
661
          msg = "test error"
662

  
663
        curl.info = {
664
          pycurl.RESPONSE_CODE: response_code,
665
          }
666

  
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)))
676

  
677
        yield (curl, msg)
678

  
679
      if use_monitor:
680
        self.assertTrue(compat.all(curl.opts["__lockcheck__"]
681
                                   for curl in handles))
682

  
683
    http.client.ProcessRequests(requests, lock_monitor_cb=lock_monitor_cb,
684
                                _curl=_FakeCurl,
685
                                _curl_multi=self._DummyCurlMulti,
686
                                _curl_process=_ProcessRequests)
687
    for req in requests:
688
      if req.port % 3 == 0:
689
        self.assertTrue(req.success)
690
        self.assertEqual(req.error, None)
691
      else:
692
        self.assertFalse(req.success)
693
        self.assertTrue("test error" in req.error)
694

  
695
    # See if monitor was disabled
696
    if use_monitor:
697
      monitor = lock_monitor_cb.GetMonitor()
698
      self.assertEqual(monitor._pending_fn, None)
699
      self.assertEqual(monitor.GetLockInfo(None), [])
700
    else:
701
      self.assertEqual(lock_monitor_cb, None)
702

  
703
    self.assertEqual(len(requests), requests_count)
704

  
705
  def testBadRequest(self):
706
    bad_request = http.client.HttpClientRequest("localhost", 27784,
707
                                                "POST", "/version")
708
    bad_request.success = False
709

  
710
    self.assertRaises(AssertionError, http.client.ProcessRequests,
711
                      [bad_request], _curl=NotImplemented,
712
                      _curl_multi=NotImplemented, _curl_process=NotImplemented)
389 713

  
390 714

  
391 715
if __name__ == '__main__':

Also available in: Unified diff