d2_test.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # Copyright (C) 2013-2014 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 init 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 TestD2Daemon(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. #
  30. # However, we do want to set the logging lock directory to somewhere
  31. # to which we can write - use the current working directory. We then
  32. # set the appropriate environment variable. os.putenv() may be not
  33. # supported on some platforms as suggested in
  34. # http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
  35. # "If the platform supports the putenv() function...". It was checked
  36. # that it does not work on Ubuntu. To overcome this problem we access
  37. # os.environ directly.
  38. lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
  39. if lockdir_envvar not in os.environ:
  40. os.environ[lockdir_envvar] = os.getcwd()
  41. def tearDown(self):
  42. pass
  43. def readPipe(self, pipe_fd):
  44. """
  45. Reads bytes from a pipe and returns a character string. If nothing is
  46. read, or if there is an error, an empty string is returned.
  47. pipe_fd - Pipe file descriptor to read
  48. """
  49. try:
  50. data = os.read(pipe_fd, 16384)
  51. # Make sure we have a string
  52. if (data is None):
  53. data = ""
  54. else:
  55. data = str(data)
  56. except OSError:
  57. data = ""
  58. return data
  59. def runCommand(self, params, wait=1):
  60. """
  61. This method runs a command and returns a tuple: (returncode, stdout, stderr)
  62. """
  63. ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
  64. print("Running command: %s" % (" ".join(params)))
  65. # redirect stdout to a pipe so we can check that our
  66. # process spawning is doing the right thing with stdout
  67. self.stdout_old = os.dup(sys.stdout.fileno())
  68. self.stdout_pipes = os.pipe()
  69. os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
  70. os.close(self.stdout_pipes[1])
  71. # do the same trick for stderr:
  72. self.stderr_old = os.dup(sys.stderr.fileno())
  73. self.stderr_pipes = os.pipe()
  74. os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
  75. os.close(self.stderr_pipes[1])
  76. # note that we use dup2() to restore the original stdout
  77. # to the main program ASAP in each test... this prevents
  78. # hangs reading from the child process (as the pipe is only
  79. # open in the child), and also insures nice pretty output
  80. pi = ProcessInfo('Test Process', params)
  81. pi.spawn()
  82. time.sleep(wait)
  83. os.dup2(self.stdout_old, sys.stdout.fileno())
  84. os.dup2(self.stderr_old, sys.stderr.fileno())
  85. self.assertNotEqual(pi.process, None)
  86. self.assertTrue(type(pi.pid) is int)
  87. # Set non-blocking read on pipes. Process may not print anything
  88. # on specific output and the we would hang without this.
  89. fd = self.stdout_pipes[0]
  90. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  91. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  92. fd = self.stderr_pipes[0]
  93. fl = fcntl.fcntl(fd, fcntl.F_GETFL)
  94. fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
  95. # As we don't know how long the subprocess will take to start and
  96. # produce output, we'll loop and sleep for 250 ms between each
  97. # iteration. To avoid an infinite loop, we'll loop for a maximum
  98. # of five seconds: that should be enough.
  99. for count in range(20):
  100. # Read something from stderr and stdout (these reads don't block).
  101. output = self.readPipe(self.stdout_pipes[0])
  102. error = self.readPipe(self.stderr_pipes[0])
  103. # If the process has already exited, or if it has output something,
  104. # quit the loop now.
  105. if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
  106. break
  107. # Process still running, try again in 250 ms.
  108. time.sleep(0.25)
  109. # Exited loop, kill the process if it is still running
  110. if pi.process.poll() is None:
  111. try:
  112. pi.process.terminate()
  113. except OSError:
  114. print("Ignoring failed kill attempt. Process is dead already.")
  115. # call this to get returncode, process should be dead by now
  116. rc = pi.process.wait()
  117. # Clean up our stdout/stderr munging.
  118. os.dup2(self.stdout_old, sys.stdout.fileno())
  119. os.close(self.stdout_old)
  120. os.close(self.stdout_pipes[0])
  121. os.dup2(self.stderr_old, sys.stderr.fileno())
  122. os.close(self.stderr_old)
  123. os.close(self.stderr_pipes[0])
  124. # Free up resources (file descriptors) from the ProcessInfo object
  125. # TODO: For some reason, this gives an error if the process has ended,
  126. # although it does cause all descriptors still allocated to the
  127. # object to be freed.
  128. pi = None
  129. print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
  130. % (rc, len(output), len(error)) )
  131. return (rc, output, error)
  132. def test_alive(self):
  133. print("Note: Simple test to verify that D2 server can be started.")
  134. # note that "-s" for stand alone is necessary in order to flush the log output
  135. # soon enough to catch it.
  136. (returncode, output, error) = self.runCommand(["../b10-dhcp-ddns", "-v"])
  137. output_text = str(output) + str(error)
  138. self.assertEqual(output_text.count("DCTL_STARTING"), 1)
  139. if __name__ == '__main__':
  140. unittest.main()