test_service.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python
  2. import os
  3. import pathlib
  4. import subprocess
  5. from contextlib import contextmanager
  6. from pathlib import Path
  7. import pytest
  8. import test_logging
  9. from conftest import CfdModes
  10. from util import select_platform, start_cloudflared, wait_tunnel_ready, write_config
  11. def default_config_dir():
  12. return os.path.join(Path.home(), ".cloudflared")
  13. def default_config_file():
  14. return os.path.join(default_config_dir(), "config.yml")
  15. class TestServiceMode:
  16. @select_platform("Darwin")
  17. @pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
  18. def test_launchd_service_log_to_file(self, tmp_path, component_tests_config):
  19. log_file = tmp_path / test_logging.default_log_file
  20. additional_config = {
  21. # On Darwin cloudflared service defaults to run classic tunnel command
  22. "hello-world": True,
  23. "logfile": str(log_file),
  24. }
  25. config = component_tests_config(additional_config=additional_config, cfd_mode=CfdModes.CLASSIC)
  26. def assert_log_file():
  27. test_logging.assert_log_in_file(log_file)
  28. test_logging.assert_json_log(log_file)
  29. self.launchd_service_scenario(config, assert_log_file)
  30. @select_platform("Darwin")
  31. @pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
  32. def test_launchd_service_with_token(self, tmp_path, component_tests_config):
  33. log_file = tmp_path / test_logging.default_log_file
  34. additional_config = {
  35. "logfile": str(log_file),
  36. }
  37. config = component_tests_config(additional_config=additional_config)
  38. # service install doesn't install the config file but in this case we want to use some default settings
  39. # so we write the base config without the tunnel credentials and ID
  40. write_config(pathlib.Path(default_config_dir()), config.base_config())
  41. self.launchd_service_scenario(config, use_token=True)
  42. @select_platform("Darwin")
  43. @pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
  44. def test_launchd_service_rotating_log(self, tmp_path, component_tests_config):
  45. log_dir = tmp_path / "logs"
  46. additional_config = {
  47. # On Darwin cloudflared service defaults to run classic tunnel command
  48. "hello-world": True,
  49. "loglevel": "debug",
  50. "log-directory": str(log_dir),
  51. }
  52. config = component_tests_config(additional_config=additional_config, cfd_mode=CfdModes.CLASSIC)
  53. def assert_rotating_log():
  54. test_logging.assert_log_to_dir(config, log_dir)
  55. self.launchd_service_scenario(config, assert_rotating_log)
  56. def launchd_service_scenario(self, config, extra_assertions=None, use_token=False):
  57. with self.run_service(Path(default_config_dir()), config, use_token=use_token):
  58. self.launchctl_cmd("list")
  59. self.launchctl_cmd("start")
  60. wait_tunnel_ready(tunnel_url=config.get_url())
  61. if extra_assertions is not None:
  62. extra_assertions()
  63. self.launchctl_cmd("stop")
  64. os.remove(default_config_file())
  65. self.launchctl_cmd("list", success=False)
  66. @select_platform("Linux")
  67. @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
  68. reason=f"There is already a config file in default path")
  69. def test_sysv_service_log_to_file(self, tmp_path, component_tests_config):
  70. log_file = tmp_path / test_logging.default_log_file
  71. additional_config = {
  72. "logfile": str(log_file),
  73. }
  74. config = component_tests_config(additional_config=additional_config)
  75. def assert_log_file():
  76. test_logging.assert_log_in_file(log_file)
  77. test_logging.assert_json_log(log_file)
  78. self.sysv_service_scenario(config, tmp_path, assert_log_file)
  79. @select_platform("Linux")
  80. @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
  81. reason=f"There is already a config file in default path")
  82. def test_sysv_service_rotating_log(self, tmp_path, component_tests_config):
  83. log_dir = tmp_path / "logs"
  84. additional_config = {
  85. "loglevel": "debug",
  86. "log-directory": str(log_dir),
  87. }
  88. config = component_tests_config(additional_config=additional_config)
  89. def assert_rotating_log():
  90. # We need the folder to have executable permissions for the "stat" command in the assertions to work.
  91. subprocess.check_call(['sudo', 'chmod', 'o+x', log_dir])
  92. test_logging.assert_log_to_dir(config, log_dir)
  93. self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
  94. @select_platform("Linux")
  95. @pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
  96. reason=f"There is already a config file in default path")
  97. def test_sysv_service_with_token(self, tmp_path, component_tests_config):
  98. additional_config = {
  99. "loglevel": "debug",
  100. }
  101. config = component_tests_config(additional_config=additional_config)
  102. # service install doesn't install the config file but in this case we want to use some default settings
  103. # so we write the base config without the tunnel credentials and ID
  104. config_path = write_config(tmp_path, config.base_config())
  105. subprocess.run(["sudo", "cp", config_path, "/etc/cloudflared/config.yml"], check=True)
  106. self.sysv_service_scenario(config, tmp_path, use_token=True)
  107. def sysv_service_scenario(self, config, tmp_path, extra_assertions=None, use_token=False):
  108. with self.run_service(tmp_path, config, root=True, use_token=use_token):
  109. self.sysv_cmd("status")
  110. wait_tunnel_ready(tunnel_url=config.get_url())
  111. if extra_assertions is not None:
  112. extra_assertions()
  113. # Service install copies config file to /etc/cloudflared/config.yml
  114. subprocess.run(["sudo", "rm", "/etc/cloudflared/config.yml"])
  115. self.sysv_cmd("status", success=False)
  116. @contextmanager
  117. def run_service(self, tmp_path, config, root=False, use_token=False):
  118. args = ["service", "install"]
  119. if use_token:
  120. args.append(config.get_token())
  121. try:
  122. service = start_cloudflared(
  123. tmp_path, config, cfd_args=args, cfd_pre_args=[], capture_output=False, root=root, skip_config_flag=use_token)
  124. yield service
  125. finally:
  126. start_cloudflared(
  127. tmp_path, config, cfd_args=["service", "uninstall"], cfd_pre_args=[], capture_output=False, root=root, skip_config_flag=use_token)
  128. def launchctl_cmd(self, action, success=True):
  129. cmd = subprocess.run(
  130. ["launchctl", action, "com.cloudflare.cloudflared"], check=success)
  131. if not success:
  132. assert cmd.returncode != 0, f"Expect {cmd.args} to fail, but it succeed"
  133. def sysv_cmd(self, action, success=True):
  134. cmd = subprocess.run(
  135. ["sudo", "service", "cloudflared", action], check=success)
  136. if not success:
  137. assert cmd.returncode != 0, f"Expect {cmd.args} to fail, but it succeed"