dhcp4_test.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # Copyright (C) 2012 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. from bind10_src import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
  16. import unittest
  17. import sys
  18. import os
  19. import signal
  20. import socket
  21. from isc.net.addr import IPAddr
  22. import time
  23. import isc
  24. import fcntl
  25. class TestDhcpv4Daemon(unittest.TestCase):
  26. def setUp(self):
  27. # don't redirect stdout/stderr here as we want to print out things
  28. # during the test
  29. pass
  30. def tearDown(self):
  31. pass
  32. def runCommand(self, params, wait=1):
  33. """
  34. This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
  35. """
  36. ## @todo: Convert this into generic method and reuse it in dhcp6
  37. print("Running command: %s" % (" ".join(params)))
  38. # redirect stdout to a pipe so we can check that our
  39. # process spawning is doing the right thing with stdout
  40. self.stdout_old = os.dup(sys.stdout.fileno())
  41. self.stdout_pipes = os.pipe()
  42. os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
  43. os.close(self.stdout_pipes[1])
  44. # do the same trick for stderr:
  45. self.stderr_old = os.dup(sys.stderr.fileno())
  46. self.stderr_pipes = os.pipe()
  47. os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
  48. os.close(self.stderr_pipes[1])
  49. # note that we use dup2() to restore the original stdout
  50. # to the main program ASAP in each test... this prevents
  51. # hangs reading from the child process (as the pipe is only
  52. # open in the child), and also insures nice pretty output
  53. pi = ProcessInfo('Test Process', params)
  54. pi.spawn()
  55. time.sleep(wait)
  56. os.dup2(self.stdout_old, sys.stdout.fileno())
  57. os.dup2(self.stderr_old, sys.stderr.fileno())
  58. self.assertNotEqual(pi.process, None)
  59. self.assertTrue(type(pi.pid) is int)
  60. # Set non-blocking read on pipes. Process may not print anything
  61. # on specific output and the we would hang without this.
  62. fd = self.stdout_pipes[0]
  63. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  64. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  65. fd = self.stderr_pipes[0]
  66. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  67. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  68. # There's potential problem if b10-dhcp4 prints out more
  69. # than 4k of text
  70. try:
  71. output = os.read(self.stdout_pipes[0], 4096)
  72. except OSError:
  73. print("No data available from stdout")
  74. output = ""
  75. # read can return None. Make sure we have a string
  76. if (output is None):
  77. output = ""
  78. try:
  79. error = os.read(self.stderr_pipes[0], 4096)
  80. except OSError:
  81. print("No data available on stderr")
  82. error = ""
  83. # read can return None. Make sure we have a string
  84. if (error is None):
  85. error = ""
  86. try:
  87. if (not pi.process.poll()):
  88. # let's be nice at first...
  89. pi.process.terminate()
  90. except OSError:
  91. print("Ignoring failed kill attempt. Process is dead already.")
  92. # call this to get returncode, process should be dead by now
  93. rc = pi.process.wait()
  94. # Clean up our stdout/stderr munging.
  95. os.dup2(self.stdout_old, sys.stdout.fileno())
  96. os.close(self.stdout_pipes[0])
  97. os.dup2(self.stderr_old, sys.stderr.fileno())
  98. os.close(self.stderr_pipes[0])
  99. print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
  100. % (rc, len(output), len(error)) )
  101. return (rc, output, error)
  102. def test_alive(self):
  103. print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
  104. print(" not that is can bind sockets correctly. Please ignore binding errors.")
  105. (returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
  106. self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
  107. def test_portnumber_0(self):
  108. print("Check that specifying port number 0 is not allowed.")
  109. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p', '0'])
  110. # When invalid port number is specified, return code must not be success
  111. self.assertTrue(returncode != 0)
  112. # Check that there is an error message about invalid port number printed on stderr
  113. self.assertEqual( str(error).count("Failed to parse port number"), 1)
  114. def test_portnumber_missing(self):
  115. print("Check that -p option requires a parameter.")
  116. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p'])
  117. # When invalid port number is specified, return code must not be success
  118. self.assertTrue(returncode != 0)
  119. # Check that there is an error message about invalid port number printed on stderr
  120. self.assertEqual( str(error).count("option requires an argument"), 1)
  121. def test_portnumber_invalid1(self):
  122. print("Check that -p option is check against bogus port number (999999).")
  123. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','999999'])
  124. # When invalid port number is specified, return code must not be success
  125. self.assertTrue(returncode != 0)
  126. # Check that there is an error message about invalid port number printed on stderr
  127. self.assertEqual( str(error).count("Failed to parse port number"), 1)
  128. def test_portnumber_invalid2(self):
  129. print("Check that -p option is check against bogus port number (123garbage).")
  130. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','123garbage'])
  131. # When invalid port number is specified, return code must not be success
  132. self.assertTrue(returncode != 0)
  133. # Check that there is an error message about invalid port number printed on stderr
  134. self.assertEqual( str(error).count("Failed to parse port number"), 1)
  135. def test_portnumber_nonroot(self):
  136. print("Check that specifying unprivileged port number will work.")
  137. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
  138. # When invalid port number is specified, return code must not be success
  139. # TODO: Temporarily commented out as socket binding on systems that do not have
  140. # interface detection implemented currently fails.
  141. # self.assertTrue(returncode == 0)
  142. # Check that there is an error message about invalid port number printed on stderr
  143. self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
  144. def test_skip_msgq(self):
  145. print("Check that connection to BIND10 msgq can be disabled.")
  146. (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
  147. # When invalid port number is specified, return code must not be success
  148. # TODO: Temporarily commented out as socket binding on systems that do not have
  149. # interface detection implemented currently fails.
  150. # self.assertTrue(returncode == 0)
  151. # Check that there is an error message about invalid port number printed on stderr
  152. self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
  153. if __name__ == '__main__':
  154. unittest.main()