teach EdenFS to auto migrate folks on Ventura

Reviewed By: xavierd

Differential Revision: D40243654

fbshipit-source-id: d5c19a8e1be2db1075f1e21c96816a5902dc9ee7
This commit is contained in:
Katie Mancini 2022-10-20 11:24:07 -07:00 committed by Facebook GitHub Bot
parent 479969e588
commit 6823a627b6
4 changed files with 188 additions and 13 deletions

View File

@ -22,7 +22,19 @@ import time
import typing
import uuid
from pathlib import Path
from typing import Any, Dict, IO, KeysView, List, Mapping, Optional, Set, Tuple, Union
from typing import (
Any,
Callable,
Dict,
IO,
KeysView,
List,
Mapping,
Optional,
Set,
Tuple,
Union,
)
import facebook.eden.ttypes as eden_ttypes
import toml
@ -32,7 +44,15 @@ from facebook.eden.ttypes import MountInfo as ThriftMountInfo, MountState
from filelock import BaseFileLock, FileLock
from . import configinterpolator, configutil, telemetry, util, version
from .util import HealthStatus, print_stderr, Spinner, write_file_atomically
from .util import (
FUSE_MOUNT_PROTOCOL_STRING,
HealthStatus,
NFS_MOUNT_PROTOCOL_STRING,
print_stderr,
PRJFS_MOUNT_PROTOCOL_STRING,
Spinner,
write_file_atomically,
)
try:
from eden.thrift import client # @manual
@ -85,7 +105,11 @@ DEFAULT_REVISION = { # supported repo name -> default bookmark
SUPPORTED_REPOS: KeysView[str] = DEFAULT_REVISION.keys()
SUPPORTED_MOUNT_PROTOCOLS = {"fuse", "nfs", "prjfs"}
SUPPORTED_MOUNT_PROTOCOLS: Set[str] = {
FUSE_MOUNT_PROTOCOL_STRING,
NFS_MOUNT_PROTOCOL_STRING,
PRJFS_MOUNT_PROTOCOL_STRING,
}
# Create a readme file with this name in the mount point directory.
# The intention is for this to contain instructions telling users what to do if their
@ -1558,6 +1582,57 @@ class EdenCheckout:
self.save_config(new_config)
# Fuse is still not functional on Ventura, so users will need to use NFS on
# Ventura.
def should_migrate_mount_protocol_to_nfs(instance: EdenInstance) -> bool:
if sys.platform != "darwin":
return False
if util.is_sandcastle():
return False
ventura_os_version = "22.0.0"
if tuple(os.uname().release.split(".")) >= tuple(ventura_os_version.split(".")):
return instance.get_config_bool("core.migrate_existing_to_nfs", default=False)
return False
# Checks for any non NFS mounts and migrates them to NFS.
def _do_nfs_migration(
instance: EdenInstance, get_migration_success_message: Callable[[str], str]
) -> None:
migrate_mounts = False
for checkout in instance.get_checkouts():
if checkout.get_config().mount_protocol != util.NFS_MOUNT_PROTOCOL_STRING:
migrate_mounts = True
if not migrate_mounts:
# most the time this should be the case. we only need to migrate mounts
# once, and then we should just be able to skip this all other times.
return
print("migrating mounts to NFS ...")
for checkout in instance.get_checkouts():
if checkout.get_config().mount_protocol != util.NFS_MOUNT_PROTOCOL_STRING:
checkout.migrate_mount_protocol(util.NFS_MOUNT_PROTOCOL_STRING)
instance.log_sample("migrate_existing_clones_to_nfs")
print(get_migration_success_message(util.NFS_MOUNT_PROTOCOL_STRING))
def _do_manual_migration(
instance: EdenInstance,
migrate_to: str,
get_migration_success_message: Callable[[str], str],
) -> None:
for checkout in instance.get_checkouts():
checkout.migrate_mount_protocol(migrate_to)
print(get_migration_success_message(migrate_to))
def detect_nested_checkout(
path: Union[str, Path],
instance: EdenInstance,

View File

@ -1699,6 +1699,8 @@ class StartCmd(Subcmd):
instance, daemon_binary, args.edenfs_args
)
if config_mod.should_migrate_mount_protocol_to_nfs(instance):
config_mod._do_nfs_migration(instance, get_migration_success_message)
return daemon.start_edenfs_service(instance, daemon_binary, args.edenfs_args)
def start_in_foreground(
@ -2029,7 +2031,11 @@ re-open these files after EdenFS is restarted.
self._do_stop(instance, old_pid, timeout=15)
if migrate_to is not None:
self._do_migration(instance, migrate_to)
config_mod._do_manual_migration(
instance, migrate_to, get_migration_success_message
)
elif config_mod.should_migrate_mount_protocol_to_nfs(instance):
config_mod._do_nfs_migration(instance, get_migration_success_message)
return self._finish_restart(instance)
def _force_restart(
@ -2068,12 +2074,6 @@ re-open these files after EdenFS is restarted.
os.kill(pid, signal.SIGTERM)
self._wait_for_stop(instance, pid, timeout)
def _do_migration(self, instance: EdenInstance, migrate_to: str) -> None:
for checkout in instance.get_checkouts():
checkout.migrate_mount_protocol(migrate_to)
print(get_migration_success_message(migrate_to))
def _finish_restart(self, instance: EdenInstance) -> int:
exit_code = daemon.start_edenfs_service(
instance, daemon_binary=self.args.daemon_binary

View File

@ -12,14 +12,15 @@ import os
import sys
import unittest
from pathlib import Path
from typing import Dict
import toml
import toml.decoder
from eden.fs.cli.config import EdenInstance
from eden.test_support.temporary_directory import TemporaryDirectoryMixin
from eden.test_support.testcase import EdenTestCaseBase
from .. import config as config_mod, configutil, util
from ..config import EdenInstance
from ..configinterpolator import EdenConfigInterpolator
from ..configutil import EdenConfigParser, UnexpectedType
@ -543,3 +544,98 @@ class EdenInstanceConstructionTest(unittest.TestCase):
)
self.assertEqual(instance.etc_eden_dir, Path("/etc/eden"))
self.assertEqual(instance.home_dir, Path("/home/testuser/"))
class NFSMigrationTest(EdenTestCaseBase):
def setUp(self) -> None:
super().setUp()
self._user = "bob"
self._state_dir = self.tmp_dir / ".eden"
self._etc_eden_dir = self.tmp_dir / "etc/eden"
self._config_d = self.tmp_dir / "etc/eden/config.d"
self._home_dir = self.tmp_dir / "home" / self._user
self._interpolate_dict = {
"USER": self._user,
"USER_ID": "42",
"HOME": str(self._home_dir),
}
self._state_dir.mkdir()
self._config_d.mkdir(exist_ok=True, parents=True)
self._home_dir.mkdir(exist_ok=True, parents=True)
def setup_config_files(self, mounts: Dict[str, str]) -> None:
config_json_list = ",\n".join(
[f'"{self.tmp_dir}/{mount}" : "{mount}"' for mount in mounts]
)
config_json = f"""{{
{config_json_list}
}}"""
(self._state_dir / "config.json").write_text(config_json)
(self._state_dir / "clients").mkdir()
for mount, initial_mount_protocol in mounts.items():
(self._state_dir / "clients" / mount).mkdir()
(self._state_dir / "clients" / mount / "config.toml").write_text(
f"""
[repository]
path = "{self.tmp_dir}/.eden-backing-repos/test"
type = "hg"
protocol = "{initial_mount_protocol}"
"""
)
def check_migrate_nfs(self, mounts: Dict[str, str]) -> None:
self.setup_config_files(mounts)
cmdline = [
b"/usr/local/libexec/eden/edenfs",
b"--edenfs",
b"--edenDir",
str(self._state_dir).encode("utf-8"),
b"--etcEdenDir",
str(self._config_d).encode("utf-8"),
b"--configPath",
str(self._home_dir).encode("utf-8"),
b"--edenfsctlPath",
b"/usr/local/bin/edenfsctl",
b"--takeover",
b"",
]
instance = config_mod.eden_instance_from_cmdline(cmdline)
for mount, initial_mount_protocol in mounts.items():
checkoutConfig = config_mod.EdenCheckout(
instance, self.tmp_dir / mount, self._state_dir / "clients" / mount
).get_config()
self.assertEqual(checkoutConfig.mount_protocol, initial_mount_protocol)
config_mod._do_nfs_migration(instance, lambda protocol: protocol)
for mount in mounts:
checkoutConfig = config_mod.EdenCheckout(
instance, self.tmp_dir / mount, self._state_dir / "clients" / mount
).get_config()
self.assertEqual(checkoutConfig.mount_protocol, "nfs")
def test_none(self) -> None:
mounts = {}
self.check_migrate_nfs(mounts)
def test_simple(self) -> None:
mounts = {"test": "fuse"}
self.check_migrate_nfs(mounts)
def test_multiple(self) -> None:
mounts = {"test1": "fuse", "test2": "fuse"}
self.check_migrate_nfs(mounts)
def test_already_nfs(self) -> None:
mounts = {"test": "nfs"}
self.check_migrate_nfs(mounts)
def test_multiple_nfs_fuse(self) -> None:
mounts = {"test1": "nfs", "test2": "fuse", "test3": "fuse", "test4": "nfs"}
self.check_migrate_nfs(mounts)

View File

@ -39,6 +39,10 @@ if sys.platform != "win32":
LOCK_FILE = "lock"
PID_FILE = "pid"
NFS_MOUNT_PROTOCOL_STRING = "nfs"
FUSE_MOUNT_PROTOCOL_STRING = "fuse"
PRJFS_MOUNT_PROTOCOL_STRING = "prjfs"
class EdenStartError(Exception):
pass
@ -711,9 +715,9 @@ def is_apple_silicon() -> bool:
def get_protocol(nfs: bool) -> str:
if sys.platform == "win32":
return "prjfs"
return PRJFS_MOUNT_PROTOCOL_STRING
else:
return "nfs" if nfs else "fuse"
return NFS_MOUNT_PROTOCOL_STRING if nfs else FUSE_MOUNT_PROTOCOL_STRING
def get_tip_commit_hash(repo: Path) -> bytes: