mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
d18ce1082e
Summary: This refactors the `edenfsctl start` command so that we more clearly split the functionality into two pieces: * Starting EdenFS as a service * Running EdenFS in the foreground In most normal situations in production the `start`, `restart`, and `stop` commands are used to manage running EdenFS as a service. In the future I believe our service management logic will start to diverge a bit more on Linux vs Mac vs Windows, and this should help isolate the service-management code a bit more cleanly. The foreground behavior is mainly only used by developers during testing and during the integration tests. Several options like `--gdb` and `--strace` are only allowed in foreground mode, and this refactoring makes that clearer. In the future we may also want to further restrict this, to allow only specifying additional custom arguments and a custom binary path when running in foreground mode. However, for now I have not updated that as I believe some of our integration tests may be exercising this behavior today. This change also cleans up some of the platform-specific code, and lets them share more of the logic to construct arguments for edenfs. With this change `edenfsctl start --foreground` now works on Windows. Reviewed By: pkaush Differential Revision: D20833244 fbshipit-source-id: 0f09d59702d8b64ca8f4fedccbc30da1c858afb4
217 lines
8.0 KiB
Python
217 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
import pathlib
|
|
import shutil
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import typing
|
|
import unittest
|
|
|
|
import pexpect
|
|
import toml
|
|
from eden.test_support.environment_variable import EnvironmentVariableMixin
|
|
from eden.test_support.temporary_directory import TemporaryDirectoryMixin
|
|
|
|
from .lib.edenfs_systemd import EdenFSSystemdMixin
|
|
from .lib.find_executables import FindExe
|
|
from .lib.pexpect import PexpectAssertionMixin
|
|
from .lib.systemd import SystemdUserServiceManagerMixin
|
|
|
|
|
|
class SystemdTest(
|
|
unittest.TestCase,
|
|
EnvironmentVariableMixin,
|
|
TemporaryDirectoryMixin,
|
|
SystemdUserServiceManagerMixin,
|
|
EdenFSSystemdMixin,
|
|
PexpectAssertionMixin,
|
|
):
|
|
"""Test Eden's systemd service for Linux."""
|
|
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
|
|
self.set_environment_variable("EDEN_EXPERIMENTAL_SYSTEMD", "1")
|
|
self.eden_dir = self.make_temporary_directory()
|
|
self.etc_eden_dir = self.make_temporary_directory()
|
|
self.home_dir = self.make_temporary_directory()
|
|
|
|
# TODO(T33122320): Delete this test when systemd is properly integrated.
|
|
def test_eden_start_with_systemd_disabled_does_not_say_systemd_mode_is_enabled(
|
|
self
|
|
) -> None:
|
|
self.unset_environment_variable("EDEN_EXPERIMENTAL_SYSTEMD")
|
|
|
|
def test(start_args: typing.List[str]) -> None:
|
|
eden_cli: str = FindExe.EDEN_CLI # pyre-ignore[9]: T38947910
|
|
with self.subTest(start_args=start_args):
|
|
start_process: "pexpect.spawn[str]" = pexpect.spawn(
|
|
eden_cli,
|
|
self.get_required_eden_cli_args()
|
|
+ ["start", "--foreground"]
|
|
+ start_args,
|
|
encoding="utf-8",
|
|
logfile=sys.stderr,
|
|
)
|
|
start_process.expect_exact("Started edenfs")
|
|
self.assertNotIn(
|
|
"Running in experimental systemd mode", start_process.before
|
|
)
|
|
subprocess.check_call(
|
|
[eden_cli]
|
|
+ self.get_required_eden_cli_args()
|
|
+ ["stop", "--timeout", "0"]
|
|
)
|
|
start_process.wait()
|
|
|
|
test(start_args=["--", "--allowRoot"])
|
|
# pyre-ignore[6]: T38947910
|
|
test(start_args=["--daemon-binary", FindExe.FAKE_EDENFS])
|
|
|
|
def test_eden_start_starts_systemd_service(self) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
subprocess.check_call(
|
|
self.get_edenfsctl_cmd()
|
|
# pyre-ignore[6]: T38947910
|
|
+ ["start", "--daemon-binary", FindExe.FAKE_EDENFS]
|
|
)
|
|
self.assert_systemd_service_is_active(eden_dir=pathlib.Path(self.eden_dir))
|
|
|
|
def test_systemd_service_is_failed_if_edenfs_crashes_on_start(self) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
self.assert_systemd_service_is_stopped(eden_dir=pathlib.Path(self.eden_dir))
|
|
subprocess.call(
|
|
self.get_edenfsctl_cmd()
|
|
+ [ # pyre-ignore[6]: T38947910
|
|
"start",
|
|
"--daemon-binary",
|
|
FindExe.FAKE_EDENFS,
|
|
"--",
|
|
"--failDuringStartup",
|
|
]
|
|
)
|
|
self.assert_systemd_service_is_failed(eden_dir=pathlib.Path(self.eden_dir))
|
|
|
|
def test_eden_start_reports_service_failure_if_edenfs_fails_during_startup(
|
|
self
|
|
) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
start_process = self.spawn_start_with_fake_edenfs(
|
|
extra_args=["--", "--failDuringStartup"]
|
|
)
|
|
start_process.expect(
|
|
r"error: Starting the fb-edenfs@.+?\.service systemd service "
|
|
r"failed \(reason: exit-code\)"
|
|
)
|
|
self.assertNotIn(
|
|
"journalctl",
|
|
start_process.before,
|
|
"journalctl doesn't work and should not be mentioned",
|
|
)
|
|
remaining_output = start_process.read()
|
|
self.assertNotIn(
|
|
"journalctl",
|
|
remaining_output,
|
|
"journalctl doesn't work and should not be mentioned",
|
|
)
|
|
|
|
def test_eden_start_reports_error_if_systemd_is_dead(self) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
systemd = self.systemd
|
|
assert systemd is not None
|
|
systemd.exit()
|
|
self.assertTrue(
|
|
(systemd.xdg_runtime_dir / "systemd" / "private").exists(),
|
|
"systemd's socket file should still exist",
|
|
)
|
|
|
|
self.spoof_user_name("testuser")
|
|
start_process = self.spawn_start_with_fake_edenfs()
|
|
start_process.expect_exact(
|
|
"error: The systemd user manager is not running. Run the following "
|
|
"command to\r\nstart it, then try again:"
|
|
)
|
|
start_process.expect_exact("sudo systemctl start user@testuser.service")
|
|
|
|
def test_eden_start_reports_error_if_systemd_is_dead_and_cleaned_up(self) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
systemd = self.systemd
|
|
assert systemd is not None
|
|
systemd.exit()
|
|
shutil.rmtree(systemd.xdg_runtime_dir)
|
|
|
|
self.spoof_user_name("testuser")
|
|
start_process = self.spawn_start_with_fake_edenfs()
|
|
start_process.expect_exact(
|
|
"error: The systemd user manager is not running. Run the following "
|
|
"command to\r\nstart it, then try again:"
|
|
)
|
|
start_process.expect_exact("sudo systemctl start user@testuser.service")
|
|
|
|
def test_eden_start_uses_fallback_if_systemd_environment_is_missing(self) -> None:
|
|
self.set_up_edenfs_systemd_service()
|
|
systemd = self.systemd
|
|
assert systemd is not None
|
|
|
|
fallback_xdg_runtime_dir = str(systemd.xdg_runtime_dir)
|
|
self.set_eden_config(
|
|
{"service": {"fallback_systemd_xdg_runtime_dir": fallback_xdg_runtime_dir}}
|
|
)
|
|
self.unset_environment_variable("XDG_RUNTIME_DIR")
|
|
|
|
start_process = self.spawn_start_with_fake_edenfs()
|
|
start_process.expect_exact(
|
|
f"warning: The XDG_RUNTIME_DIR environment variable is not set; "
|
|
f"using fallback: '{fallback_xdg_runtime_dir}'"
|
|
)
|
|
start_process.expect_exact("Started edenfs")
|
|
self.assert_process_succeeds(start_process)
|
|
self.assert_systemd_service_is_active(eden_dir=pathlib.Path(self.eden_dir))
|
|
|
|
edenfs_log = pathlib.Path(self.eden_dir) / "logs" / "edenfs.log"
|
|
log_contents = edenfs_log.read_text(encoding="utf-8", errors="replace")
|
|
self.assertIn("Running in experimental systemd mode", log_contents)
|
|
|
|
def spawn_start_with_fake_edenfs(
|
|
self, extra_args: typing.Sequence[str] = ()
|
|
) -> "pexpect.spawn[str]":
|
|
return pexpect.spawn(
|
|
# pyre-ignore[6]: T38947910
|
|
FindExe.EDEN_CLI,
|
|
self.get_required_eden_cli_args()
|
|
# pyre-ignore[6]: T38947910
|
|
+ ["start", "--daemon-binary", FindExe.FAKE_EDENFS] + list(extra_args),
|
|
encoding="utf-8",
|
|
logfile=sys.stderr,
|
|
)
|
|
|
|
def get_edenfsctl_cmd(self) -> typing.List[str]:
|
|
# pyre-ignore[6,7]: T38947910
|
|
return [FindExe.EDEN_CLI] + self.get_required_eden_cli_args()
|
|
|
|
def get_required_eden_cli_args(self) -> typing.List[str]:
|
|
return [
|
|
"--config-dir",
|
|
self.eden_dir,
|
|
"--etc-eden-dir",
|
|
self.etc_eden_dir,
|
|
"--home-dir",
|
|
self.home_dir,
|
|
]
|
|
|
|
def set_eden_config(self, config) -> None:
|
|
config_d = pathlib.Path(self.etc_eden_dir) / "config.d"
|
|
config_d.mkdir()
|
|
with open(config_d / "systemd.toml", "w") as config_file:
|
|
# pyre-ignore[6]: T39129461
|
|
toml.dump(config, config_file)
|
|
|
|
def spoof_user_name(self, user_name: str) -> None:
|
|
self.set_environment_variable("LOGNAME", user_name)
|
|
self.set_environment_variable("USER", user_name)
|