nsupdate.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 lettuce import *
  16. import subprocess
  17. import re
  18. def run_nsupdate(commands):
  19. """Run nsupdate.
  20. Parameters:
  21. commands: a sequence of strings which will be sent.
  22. update_address: address to send the update to
  23. update_port: port to send the update to
  24. zone: zone to update
  25. Appends 'send' and 'quit' as final commands.
  26. nsupdate's stdout and stderr streams are stored (as one multiline string
  27. in world.last_nsupdate_stdout/stderr.
  28. The return code is stored in world.last_nsupdate_returncode
  29. (it is not checked here, since a number of tests intentionally
  30. result in a non-zero return code).
  31. """
  32. commands.append('send')
  33. commands.append('quit')
  34. args = ['nsupdate' ]
  35. nsupdate = subprocess.Popen(args, 1, None, subprocess.PIPE,
  36. subprocess.PIPE, subprocess.PIPE)
  37. for line in commands:
  38. nsupdate.stdin.write(line + "\n")
  39. (stdout, stderr) = nsupdate.communicate()
  40. world.last_nsupdate_returncode = nsupdate.returncode
  41. world.last_nsupdate_stdout = stdout
  42. world.last_nsupdate_stderr = stderr
  43. @step('send a DDNS update for (\S+) with the following commands:')
  44. def send_multiple_commands(step, zone):
  45. """
  46. Run nsupdate, and send it the given multiline set of commands.
  47. A send and quit command is always appended.
  48. This is the most 'raw' wrapper around the nsupdate call; every
  49. command except the final send needs to be specified. Intended
  50. for those tests that have unique properties.
  51. """
  52. commands = step.multiline.split("\n")
  53. run_nsupdate(commands, zone)
  54. @step('DDNS response should be ([A-Z]+)')
  55. def check_ddns_response(step, response):
  56. """
  57. Checks the result of the last call to nsupdate.
  58. If the given response argument is SUCCESS, it simply checks whether
  59. the return code from nsupdate is 0 (there is no output in that case).
  60. If not, it checks whether it is not 0, and if the given response string
  61. matches a line 'update failed: <response>' in the stderr output of
  62. nsupdate.
  63. Prints exit code, stdout and stderr streams of nsupdate if it fails.
  64. """
  65. # For success, nsupdate is silent, only check result code 0
  66. if response == "SUCCESS":
  67. assert 0 == world.last_nsupdate_returncode,\
  68. "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
  69. "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
  70. "stderr:\n" + str(world.last_nsupdate_stderr)
  71. else:
  72. found = False
  73. for line in world.last_nsupdate_stderr.split('\n'):
  74. if line == "update failed: " + response:
  75. found = True
  76. assert found and (0 != world.last_nsupdate_returncode),\
  77. "Response " + response + " not found in nsupdate output\n" +\
  78. "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
  79. "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
  80. "stderr:\n" + str(world.last_nsupdate_stderr)
  81. # Individual steps to create a DDNS update packet through nsupdate
  82. @step('Prepare a DDNS update(?: for (\S+))?(?: to (\S+)(?: port ([0-9]+)))?')
  83. def prepare_update(step, zone, server, port):
  84. """
  85. Prepares an nsupdate command that sets up an update to a server
  86. for a zone. The update is not sent yet, but the commands
  87. are stored in world.nsupdate_commands.
  88. """
  89. commands = []
  90. if server is not None:
  91. commands.append("server " + server)
  92. else:
  93. commands.append("server 127.0.0.1")
  94. if port is not None:
  95. commands[0] = commands[0] + " " + port
  96. else:
  97. commands[0] = commands[0] + " 56176"
  98. if zone is not None:
  99. commands.append("zone " + zone)
  100. world.nsupdate_commands = commands
  101. @step('Add to the DDNS update: (.*)')
  102. def add_line_to_ddns_update(step, line):
  103. """
  104. Adds a single line to the prepared nsupdate. It is not sent yet.
  105. The line must conform to nsupdate syntax.
  106. """
  107. world.nsupdate_commands.append(line)
  108. @step('Add the following lines to the DDNS update:')
  109. def add_lines_to_ddns_update(step, line):
  110. """
  111. Adds multiple lines to the prepared nsupdate. It is not sent yet.
  112. The lines must conform to nsupdate syntax.
  113. """
  114. world.nsupdate_commands.extend(step.multiline.split('\n'))
  115. @step('Send the DDNS update')
  116. def run_ddns_update(step):
  117. """
  118. Runs the prepared nsupdate, see run_nsupdate() for more information.
  119. """
  120. run_nsupdate(world.nsupdate_commands)
  121. @step('use DDNS to set the SOA SERIAL to ([0-9]+)')
  122. def set_serial_to(step, new_serial):
  123. """
  124. Convenience compound step; prepare an update for example.org,
  125. which sets the SERIAL to the given value, and send it.
  126. It makes no other changes, and has hardcoded values for the other
  127. SOA rdata fields.
  128. """
  129. step.given('Prepare a DDNS update')
  130. step.given('add to the DDNS update: update add example.org 3600 IN SOA ns1.example.org. admin.example.org. ' + new_serial + ' 3600 1800 2419200 7200')
  131. step.given('Send the DDNS update')
  132. @step('use DDNS to add a record (.*)')
  133. def add_record(step, new_record):
  134. """
  135. Convenience compound step; prepare an update for example.org,
  136. which adds one record, then send it.
  137. Apart from the update addition, the update will not contain anything else.
  138. """
  139. step.given('Prepare a DDNS update')
  140. step.given('add to the DDNS update: update add ' + new_record)
  141. step.given('Send the DDNS update')
  142. @step('set DDNS ACL ([0-9]+) for ([0-9.]+) to ([A-Z]+)')
  143. def set_ddns_acl_to(step, nr, address, action):
  144. """
  145. Convenience step to update a single ACL for DDNS.
  146. Replaces the ACL at the given index for the given
  147. address, to the given action
  148. """
  149. step.given('set bind10 configuration DDNS/zones[' + nr + ']/update_acl to [{"action": "' + action + '", "from": "' + address + '"}]')
  150. step.given('last bindctl output should not contain Error')