bind10_control.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. import json
  19. @step('start bind10(?: with configuration (\S+))?' +\
  20. '(?: with cmdctl port (\d+))?' +\
  21. '(?: with msgq socket file (\S+))?' +\
  22. '(?: as (\S+))?')
  23. def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
  24. """
  25. Start BIND 10 with the given optional config file, cmdctl port, and
  26. store the running process in world with the given process name.
  27. Parameters:
  28. config_file ('with configuration <file>', optional): this configuration
  29. will be used. The path is relative to the base lettuce
  30. directory.
  31. cmdctl_port ('with cmdctl port <portnr>', optional): The port on which
  32. b10-cmdctl listens for bindctl commands. Defaults to 47805.
  33. msgq_sockfile ('with msgq socket file', optional): The msgq socket file
  34. that will be used for internal communication
  35. process_name ('as <name>', optional). This is the name that can be used
  36. in the following steps of the scenario to refer to this
  37. BIND 10 instance. Defaults to 'bind10'.
  38. This call will block until BIND10_STARTUP_COMPLETE or BIND10_STARTUP_ERROR
  39. is logged. In the case of the latter, or if it times out, the step (and
  40. scenario) will fail.
  41. It will also fail if there is a running process with the given process_name
  42. already.
  43. """
  44. args = [ 'bind10', '-v' ]
  45. if config_file is not None:
  46. args.append('-p')
  47. args.append("configurations/")
  48. args.append('-c')
  49. args.append(config_file)
  50. if cmdctl_port is None:
  51. args.append('--cmdctl-port=47805')
  52. else:
  53. args.append('--cmdctl-port=' + cmdctl_port)
  54. if process_name is None:
  55. process_name = "bind10"
  56. else:
  57. args.append('-m')
  58. args.append(process_name + '_msgq.socket')
  59. world.processes.add_process(step, process_name, args)
  60. # check output to know when startup has been completed
  61. (message, line) = world.processes.wait_for_stderr_str(process_name,
  62. ["BIND10_STARTUP_COMPLETE",
  63. "BIND10_STARTUP_ERROR"])
  64. assert message == "BIND10_STARTUP_COMPLETE", "Got: " + str(line)
  65. @step('wait for bind10 auth (?:of (\w+) )?to start')
  66. def wait_for_auth(step, process_name):
  67. """Wait for b10-auth to run. This is done by blocking until the message
  68. AUTH_SERVER_STARTED is logged.
  69. Parameters:
  70. process_name ('of <name', optional): The name of the BIND 10 instance
  71. to wait for. Defaults to 'bind10'.
  72. """
  73. if process_name is None:
  74. process_name = "bind10"
  75. world.processes.wait_for_stderr_str(process_name, ['AUTH_SERVER_STARTED'],
  76. False)
  77. @step('wait for bind10 xfrout (?:of (\w+) )?to start')
  78. def wait_for_xfrout(step, process_name):
  79. """Wait for b10-xfrout to run. This is done by blocking until the message
  80. XFROUT_NEW_CONFIG_DONE is logged.
  81. Parameters:
  82. process_name ('of <name', optional): The name of the BIND 10 instance
  83. to wait for. Defaults to 'bind10'.
  84. """
  85. if process_name is None:
  86. process_name = "bind10"
  87. world.processes.wait_for_stderr_str(process_name,
  88. ['XFROUT_NEW_CONFIG_DONE'],
  89. False)
  90. @step('have bind10 running(?: with configuration ([\S]+))?' +\
  91. '(?: with cmdctl port (\d+))?' +\
  92. '(?: as ([\S]+))?')
  93. def have_bind10_running(step, config_file, cmdctl_port, process_name):
  94. """
  95. Compound convenience step for running bind10, which consists of
  96. start_bind10 and wait_for_auth.
  97. Currently only supports the 'with configuration' option.
  98. """
  99. start_step = 'start bind10 with configuration ' + config_file
  100. wait_step = 'wait for bind10 auth to start'
  101. if cmdctl_port is not None:
  102. start_step += ' with cmdctl port ' + str(cmdctl_port)
  103. if process_name is not None:
  104. start_step += ' as ' + process_name
  105. wait_step = 'wait for bind10 auth of ' + process_name + ' to start'
  106. step.given(start_step)
  107. step.given(wait_step)
  108. # function to send lines to bindctl, and store the result
  109. def run_bindctl(commands, cmdctl_port=None):
  110. """Run bindctl.
  111. Parameters:
  112. commands: a sequence of strings which will be sent.
  113. cmdctl_port: a port number on which cmdctl is listening, is converted
  114. to string if necessary. If not provided, or None, defaults
  115. to 47805
  116. bindctl's stdout and stderr streams are stored (as one multiline string
  117. in world.last_bindctl_stdout/stderr.
  118. Fails if the return code is not 0
  119. """
  120. if cmdctl_port is None:
  121. cmdctl_port = 47805
  122. args = ['bindctl', '-p', str(cmdctl_port)]
  123. bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
  124. subprocess.PIPE, None)
  125. for line in commands:
  126. bindctl.stdin.write(line + "\n")
  127. (stdout, stderr) = bindctl.communicate()
  128. result = bindctl.returncode
  129. world.last_bindctl_stdout = stdout
  130. world.last_bindctl_stderr = stderr
  131. assert result == 0, "bindctl exit code: " + str(result) +\
  132. "\nstdout:\n" + str(stdout) +\
  133. "stderr:\n" + str(stderr)
  134. @step('last bindctl( stderr)? output should( not)? contain (\S+)')
  135. def check_bindctl_output(step, stderr, notv, string):
  136. """Checks the stdout (or stderr) stream of the last run of bindctl,
  137. fails if the given string is not found in it (or fails if 'not' was
  138. set and it is found
  139. Parameters:
  140. stderr ('stderr'): Check stderr instead of stdout output
  141. notv ('not'): reverse the check (fail if string is found)
  142. string ('contain <string>') string to look for
  143. """
  144. if stderr is None:
  145. output = world.last_bindctl_stdout
  146. else:
  147. output = world.last_bindctl_stderr
  148. found = False
  149. if string in output:
  150. found = True
  151. if notv is None:
  152. assert found == True, "'" + string +\
  153. "' was not found in bindctl output:\n" +\
  154. output
  155. else:
  156. assert not found, "'" + string +\
  157. "' was found in bindctl output:\n" +\
  158. output
  159. def parse_bindctl_output_as_data_structure():
  160. """Helper function for data-related command tests: evaluates the
  161. last output of bindctl as a data structure that can then be
  162. inspected.
  163. If the bindctl output is not valid (json) data, this call will
  164. fail with an assertion failure.
  165. If it is valid, it is parsed and returned as whatever data
  166. structure it represented.
  167. """
  168. # strip the 'Exit from bindctl' message. Why is it even there?
  169. output = world.last_bindctl_stdout.replace("Exit from bindctl", "")
  170. try:
  171. return json.loads(output)
  172. except ValueError as ve:
  173. assert False, "Last bindctl output does not appear to be a " +\
  174. "parseable data structure: " + str(ve)
  175. @step("remember the pid of process ([\S]+)")
  176. def remember_pid(step, process_name):
  177. """Stores the PID of the process with the given name as returned by
  178. Boss show_processes command.
  179. Fails if the process with the given name does not appear to exist.
  180. Stores the component_name->pid value in the dict world.process_pids.
  181. This should only be used by the related step
  182. 'the pid of process <name> should (not) have changed'
  183. Arguments:
  184. process name ('process <name>') the name of the component to store
  185. the pid of.
  186. """
  187. step.given('send bind10 the command Boss show_processes')
  188. running_processes = parse_bindctl_output_as_data_structure()
  189. # show_processes output is a list of lists, where the inner lists
  190. # are of the form [ pid, "name" ]
  191. # Not checking data form; errors will show anyway (if these turn
  192. # out to be too vague, we can change this)
  193. found = False
  194. if world.process_pids is None:
  195. world.process_pids = {}
  196. for process in running_processes:
  197. if process[1] == process_name:
  198. world.process_pids[process_name] = process[0]
  199. found = True
  200. assert found, "Process named " + process_name +\
  201. " not found in output of Boss show_processes";
  202. @step('pid of process ([\S]+) should not have changed')
  203. def check_pid(step, process_name):
  204. """Checks the PID of the process with the given name as returned by
  205. Boss show_processes command.
  206. Fails if the process with the given name does not appear to exist.
  207. Fails if the process with the given name exists, but has a different
  208. pid than it had when the step 'remember the pid of process' was
  209. called.
  210. Fails if that step has not been called (since world.process_pids
  211. does not exist).
  212. """
  213. step.given('send bind10 the command Boss show_processes')
  214. running_processes = parse_bindctl_output_as_data_structure()
  215. # show_processes output is a list of lists, where the inner lists
  216. # are of the form [ pid, "name" ]
  217. # Not checking data form; errors will show anyway (if these turn
  218. # out to be too vague, we can change this)
  219. found = False
  220. assert world.process_pids is not None, "No process pids stored"
  221. assert process_name in world.process_pids, "Process named " +\
  222. process_name +\
  223. " was not stored"
  224. for process in running_processes:
  225. if process[1] == process_name:
  226. assert world.process_pids[process_name] == process[0],\
  227. "Expected pid: " + str(world.process_pids[process_name]) +\
  228. " Got pid: " + str(process[0])
  229. found = True
  230. assert found, "Process named " + process_name +\
  231. " not found in output of Boss show_processes";
  232. @step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
  233. def config_set_command(step, name, value, cmdctl_port):
  234. """
  235. Run bindctl, set the given configuration to the given value, and commit it.
  236. Parameters:
  237. name ('configuration <name>'): Identifier of the configuration to set
  238. value ('to <value>'): value to set it to.
  239. cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
  240. the command to. Defaults to 47805.
  241. Fails if cmdctl does not exit with status code 0.
  242. """
  243. commands = ["config set " + name + " " + value,
  244. "config commit",
  245. "quit"]
  246. run_bindctl(commands, cmdctl_port)
  247. @step('send bind10 the following commands(?: with cmdctl port (\d+))?')
  248. def send_multiple_commands(step, cmdctl_port):
  249. """
  250. Run bindctl, and send it the given multiline set of commands.
  251. A quit command is always appended.
  252. cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
  253. the command to. Defaults to 47805.
  254. Fails if cmdctl does not exit with status code 0.
  255. """
  256. commands = step.multiline.split("\n")
  257. # Always add quit
  258. commands.append("quit")
  259. run_bindctl(commands, cmdctl_port)
  260. @step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
  261. def config_remove_command(step, name, value, cmdctl_port):
  262. """
  263. Run bindctl, remove the given configuration item, and commit it.
  264. Parameters:
  265. name ('configuration <name>'): Identifier of the configuration to remove
  266. value ('value <value>'): if name is a named set, use value to identify
  267. item to remove
  268. cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
  269. the command to. Defaults to 47805.
  270. Fails if cmdctl does not exit with status code 0.
  271. """
  272. cmd = "config remove " + name
  273. if value is not None:
  274. cmd = cmd + " " + value
  275. commands = [cmd,
  276. "config commit",
  277. "quit"]
  278. run_bindctl(commands, cmdctl_port)
  279. @step('send bind10 the command (.+)(?: with cmdctl port (\d+))?')
  280. def send_command(step, command, cmdctl_port):
  281. """
  282. Run bindctl, send the given command, and exit bindctl.
  283. Parameters:
  284. command ('the command <command>'): The command to send.
  285. cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
  286. the command to. Defaults to 47805.
  287. Fails if cmdctl does not exit with status code 0.
  288. """
  289. commands = [command,
  290. "quit"]
  291. run_bindctl(commands, cmdctl_port)
  292. @step('bind10 module (\S+) should( not)? be running')
  293. def module_is_running(step, name, not_str):
  294. """
  295. Convenience step to check if a module is running; can only work with
  296. default cmdctl port; sends a 'help' command with bindctl, then
  297. checks if the output contains the given name.
  298. Parameters:
  299. name ('module <name>'): The name of the module (case sensitive!)
  300. not ('not'): Reverse the check (fail if it is running)
  301. """
  302. if not_str is None:
  303. not_str = ""
  304. step.given('send bind10 the command help')
  305. step.given('last bindctl output should' + not_str + ' contain ' + name)