nsupdate.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Copyright (C) 2011 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, zone="example.org.", update_address="127.0.0.1",
  19. update_port="47806"):
  20. """Run nsupdate.
  21. Parameters:
  22. commands: a sequence of strings which will be sent.
  23. update_address: adress to send the update to
  24. update_port: port to send the update to
  25. zone: zone to update
  26. Automatically adds the command to set server
  27. Appends 'send' as a final command.
  28. nsupdate's stdout and stderr streams are stored (as one multiline string
  29. in world.last_nsupdate_stdout/stderr.
  30. Fails if the return code is not 0
  31. """
  32. #commands.insert(0, "server " + update_address + " " + update_port)
  33. #commands.insert(0, "zone " + zone)
  34. commands.append('send')
  35. commands.append('quit')
  36. args = ['nsupdate' ]
  37. nsupdate = subprocess.Popen(args, 1, None, subprocess.PIPE,
  38. subprocess.PIPE, subprocess.PIPE)
  39. for line in commands:
  40. nsupdate.stdin.write(line + "\n")
  41. (stdout, stderr) = nsupdate.communicate()
  42. world.last_nsupdate_returncode = nsupdate.returncode
  43. world.last_nsupdate_stdout = stdout
  44. world.last_nsupdate_stderr = stderr
  45. #assert result == 0, "nsupdate exit code: " + str(result) +\
  46. # "\nstdout:\n" + str(stdout) +\
  47. # "stderr:\n" + str(stderr)
  48. @step('send a DDNS update for (\S+) with the following commands:')
  49. def send_multiple_commands(step, zone):
  50. """
  51. Run nsupdate, and send it the given multiline set of commands.
  52. A quit command is always appended.
  53. cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
  54. the command to. Defaults to 47805.
  55. Fails if cmdctl does not exit with status code 0.
  56. """
  57. commands = step.multiline.split("\n")
  58. run_nsupdate(commands, zone)
  59. @step('DDNS response should be ([A-Z]+)')
  60. def check_ddns_response(step, response):
  61. # For success, nsupdate is silent, only check result code 0
  62. if response == "SUCCESS":
  63. assert 0 == world.last_nsupdate_returncode,\
  64. "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
  65. "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
  66. "stderr:\n" + str(world.last_nsupdate_stderr)
  67. else:
  68. found = False
  69. for line in world.last_nsupdate_stderr.split('\n'):
  70. if line == "update failed: " + response:
  71. found = True
  72. assert found and (2 == world.last_nsupdate_returncode),\
  73. "Response " + response + " not found in nsupdate output\n" +\
  74. "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
  75. "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
  76. "stderr:\n" + str(world.last_nsupdate_stderr)
  77. # Individual steps to create a DDNS update packet through nsupdate
  78. @step('Prepare a DDNS update(?: for (\S+))?(?: to (\S+)(?: port ([0-9]+)))?')
  79. def prepare_update(step, zone, server, port):
  80. '''
  81. Prepares an nsupdate command that sets up an update to a server
  82. for a zone. The update is not sent yet, but the commands
  83. are stored in world.nsupdate_commands
  84. '''
  85. commands = []
  86. if server is not None:
  87. commands.append("server " + server)
  88. else:
  89. commands.append("server 127.0.0.1")
  90. if port is not None:
  91. commands[0] = commands[0] + " " + port
  92. else:
  93. commands[0] = commands[0] + " 47806"
  94. if zone is not None:
  95. commands.append("zone " + zone)
  96. world.nsupdate_commands = commands
  97. @step('Add to the DDNS update: (.*)')
  98. def add_line_to_ddns_update(step, line):
  99. world.nsupdate_commands.append(line)
  100. @step('Add the following lines to the DDNS update:')
  101. def add_lines_to_ddns_update(step, line):
  102. world.nsupdate_commands.extend(step.multiline.split('\n'))
  103. @step('Run the DDNS update')
  104. def run_ddns_update(step):
  105. '''
  106. Executes nsupdate as prepared by the previous steps
  107. '''
  108. run_nsupdate(world.nsupdate_commands)
  109. @step('Configure BIND10 to run DDNS')
  110. def configure_ddns_on(step):
  111. step.behave_as("""
  112. When I send bind10 the following commands
  113. \"\"\"
  114. config add Boss/components b10-ddns
  115. config set Boss/components/b10-ddns/kind dispensable
  116. config set Boss/components/b10-ddns/address DDNS
  117. config commit
  118. \"\"\"
  119. """)
  120. @step('Configure BIND10 to stop running DDNS')
  121. def configure_ddns_on(step):
  122. step.behave_as("""
  123. When I send bind10 the following commands
  124. \"\"\"
  125. config remove Boss/components b10-ddns
  126. config commit
  127. \"\"\"
  128. """)
  129. @step('use DDNS to set the SOA SERIAL to ([0-9]+)')
  130. def set_serial_to(step, new_serial):
  131. '''
  132. Convenience compound step; prepare an update for example.org,
  133. which sets the SERIAL to the given value
  134. '''
  135. step.given('Prepare a DDNS update')
  136. 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')
  137. step.given('Run the DDNS update')
  138. @step('use DDNS to add a record (.*)')
  139. def set_serial_to(step, new_record):
  140. '''
  141. Convenience compound step; prepare an update for example.org,
  142. which adds one record, then runs the update.
  143. '''
  144. step.given('Prepare a DDNS update')
  145. step.given('add to the DDNS update: update add ' + new_record)
  146. step.given('Run the DDNS update')
  147. @step('set DDNS ACL ([0-9]+) for ([0-9.]+) to ([A-Z]+)')
  148. def set_ddns_acl_to(step, nr, address, action):
  149. '''
  150. Replaces the ACL at the given index for the given
  151. address, to the given action
  152. '''
  153. step.given('set bind10 configuration DDNS/zones[' + nr + ']/update_acl to [{"action": "' + action + '", "from": "' + address + '"}]')
  154. step.given('last bindctl output should not contain Error')