Exemple #1
0
    def test_unconfigured_mounts_dont_crash(self):
        # If Eden advertises that a mount is active, but it is not in the
        # configuration, then at least don't throw an exception.
        tmp_dir = tempfile.mkdtemp(prefix='eden_test.')
        try:
            edenfs_path1 = os.path.join(tmp_dir, 'path1')
            edenfs_path2 = os.path.join(tmp_dir, 'path2')

            mount_paths = OrderedDict()
            mount_paths[edenfs_path1] = {
                'bind-mounts': {},
                'mount': edenfs_path1,
                'scm_type': 'hg',
                'snapshot': 'abcd' * 10,
                'client-dir': '/I_DO_NOT_EXIST1'
            }
            # path2 is not configured in the config...
            config = FakeConfig(mount_paths, is_healthy=True)
            # ... but is advertised by the daemon...
            config.get_thrift_client()._mounts = [
                eden_ttypes.MountInfo(mountPoint=edenfs_path1),
                eden_ttypes.MountInfo(mountPoint=edenfs_path2),
            ]

            # ... and is in the system mount table.
            mount_table = FakeMountTable()
            mount_table.stats[edenfs_path1] = mtab.MTStat(st_uid=os.getuid(),
                                                          st_dev=11)
            mount_table.stats[edenfs_path2] = mtab.MTStat(st_uid=os.getuid(),
                                                          st_dev=12)

            os.mkdir(edenfs_path1)
            hg_dir = os.path.join(edenfs_path1, '.hg')
            os.mkdir(hg_dir)
            dirstate = os.path.join(hg_dir, 'dirstate')
            dirstate_hash = b'\xab\xcd' * 10
            parents = (dirstate_hash, b'\x00' * 20)
            with open(dirstate, 'wb') as f:
                eden.dirstate.write(f, parents, tuples_dict={}, copymap={})

            dry_run = False
            out = io.StringIO()
            exit_code = doctor.cure_what_ails_you(config, dry_run, out,
                                                  mount_table)
        finally:
            shutil.rmtree(tmp_dir)

        self.assertEqual(
            f'''\
Performing 3 checks for {edenfs_path1}.
All is well.
''', out.getvalue())
        self.assertEqual(0, exit_code)
Exemple #2
0
    def test_not_all_mounts_have_watchman_watcher(self, mock_watchman):
        edenfs_path = '/path/to/eden-mount'
        edenfs_path_not_watched = '/path/to/eden-mount-not-watched'
        side_effects: List[Dict[str, Any]] = []
        calls = []

        calls.append(call(['watch-list']))
        side_effects.append({'roots': [edenfs_path]})
        calls.append(call(['watch-project', edenfs_path]))
        side_effects.append({'watcher': 'eden'})
        calls.append(call(['debug-get-subscriptions', edenfs_path]))
        side_effects.append({})
        mock_watchman.side_effect = side_effects

        out = io.StringIO()
        dry_run = False
        mount_paths = OrderedDict()
        mount_paths[edenfs_path] = {
            'bind-mounts': {},
            'mount': edenfs_path,
            'scm_type': 'git',
            'snapshot': 'abcd' * 10,
            'client-dir': '/I_DO_NOT_EXIST'
        }
        mount_paths[edenfs_path_not_watched] = {
            'bind-mounts': {},
            'mount': edenfs_path_not_watched,
            'scm_type': 'git',
            'snapshot': 'abcd' * 10,
            'client-dir': '/I_DO_NOT_EXIST'
        }
        config = FakeConfig(mount_paths, is_healthy=True)
        config.get_thrift_client()._mounts = [
            eden_ttypes.MountInfo(mountPoint=edenfs_path),
            eden_ttypes.MountInfo(mountPoint=edenfs_path_not_watched),
        ]
        mount_table = FakeMountTable()
        mount_table.stats['/path/to/eden-mount'] = mtab.MTStat(
            st_uid=os.getuid(), st_dev=10)
        mount_table.stats['/path/to/eden-mount-not-watched'] = mtab.MTStat(
            st_uid=os.getuid(), st_dev=11)
        exit_code = doctor.cure_what_ails_you(config,
                                              dry_run,
                                              out,
                                              mount_table=mount_table)

        self.assertEqual(
            'Performing 2 checks for /path/to/eden-mount.\n'
            'Performing 2 checks for /path/to/eden-mount-not-watched.\n'
            'All is well.\n', out.getvalue())
        mock_watchman.assert_has_calls(calls)
        self.assertEqual(0, exit_code)
Exemple #3
0
    def clone(self, repo_name, path, snapshot_id):
        if path in self._get_directory_map():
            raise Exception('mount path %s already exists.' % path)

        # Make sure that path is a valid destination for the clone.
        st = None
        try:
            st = os.stat(path)
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                # Note that this could also throw if path is /a/b/c and /a
                # exists, but it is a file.
                util.mkdir_p(path)
            else:
                raise

        # Note that st will be None if `mkdir_p` was run in the catch block.
        if st:
            if stat.S_ISDIR(st.st_mode):
                # If an existing directory was specified, then verify it is
                # empty.
                if len(os.listdir(path)) > 0:
                    raise OSError(errno.ENOTEMPTY, os.strerror(errno.ENOTEMPTY),
                                  path)
            else:
                # Throw because it exists, but it is not a directory.
                raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)

        # Create client directory
        dir_name = hashlib.sha1(path.encode('utf-8')).hexdigest()
        client_dir = os.path.join(self._get_clients_dir(), dir_name)
        util.mkdir_p(client_dir)

        # Store repository name in local edenrc config file
        self._store_repo_name(client_dir, repo_name)

        # Store snapshot ID
        if snapshot_id:
            client_snapshot = os.path.join(client_dir, SNAPSHOT)
            with open(client_snapshot, 'w') as f:
                f.write(snapshot_id + '\n')
        else:
            raise Exception('snapshot id not provided')

        # Create bind mounts directories
        repo_data = self.get_repo_data(repo_name)
        bind_mounts_dir = os.path.join(client_dir, 'bind-mounts')
        util.mkdir_p(bind_mounts_dir)
        for mount in repo_data['bind-mounts']:
            util.mkdir_p(os.path.join(bind_mounts_dir, mount))

        # Prepare to mount
        mount_info = eden_ttypes.MountInfo(mountPoint=path,
                                           edenClientPath=client_dir)
        with self.get_thrift_client() as client:
            client.mount(mount_info)

        # Add mapping of mount path to client directory in config.json
        self._add_path_to_directory_map(path, dir_name)
Exemple #4
0
    def mount(self, path: str) -> int:
        # Load the config info for this client, to make sure we
        # know about the client.
        path = os.path.realpath(path)
        client_dir = self._get_client_dir_for_mount_point(path)

        # Call _get_client_config() for the side-effect of it raising an
        # Exception if the config is in an invalid state.
        self._get_client_config(client_dir)

        # Make sure the mount path exists
        util.mkdir_p(path)

        # Check if it is already mounted.
        try:
            root = os.path.join(path, ".eden", "root")
            target = readlink_retry_estale(root)
            if target == path:
                print_stderr(
                    "ERROR: Mount point in use! "
                    "{} is already mounted by Eden.", path)
                return 1
            else:
                # If we are here, MOUNT/.eden/root is a symlink, but it does not
                # point to MOUNT. This suggests `path` is a subdirectory of an
                # existing mount, though we should never reach this point
                # because _get_client_dir_for_mount_point() above should have
                # already thrown an exception. We return non-zero here just in
                # case.
                print_stderr(
                    "ERROR: Mount point in use! "
                    "{} is already mounted by Eden as part of {}.",
                    path,
                    root,
                )
                return 1
        except OSError as ex:
            err = ex.errno
            if err != errno.ENOENT and err != errno.EINVAL:
                raise

        # Ask eden to mount the path
        mount_info = eden_ttypes.MountInfo(
            mountPoint=os.fsencode(path),
            edenClientPath=os.fsencode(client_dir))
        with self.get_thrift_client() as client:
            client.mount(mount_info)

        return 0
Exemple #5
0
    def mount(self, path):
        # Load the config info for this client, to make sure we
        # know about the client.
        path = os.path.realpath(path)
        client_dir = self._get_client_dir_for_mount_point(path)
        self._get_repo_name(client_dir)

        # Make sure the mount path exists
        util.mkdir_p(path)

        # Ask eden to mount the path
        mount_info = eden_ttypes.MountInfo(mountPoint=path,
                                           edenClientPath=client_dir)
        with self.get_thrift_client() as client:
            client.mount(mount_info)
Exemple #6
0
    def clone(self, client_config: ClientConfig, path: str,
              snapshot_id: str) -> None:
        if path in self._get_directory_map():
            raise Exception("""\
mount path %s is already configured (see `eden list`). \
Do you want to run `eden mount %s` instead?""" % (path, path))

        # Create the mount point directory
        self._create_mount_point_dir(path)

        # Create client directory
        clients_dir = self._get_clients_dir()
        util.mkdir_p(clients_dir)  # This directory probably already exists.
        client_dir = self._create_client_dir_for_path(clients_dir, path)

        # Store snapshot ID
        if snapshot_id:
            client_snapshot = os.path.join(client_dir, SNAPSHOT)
            with open(client_snapshot, "wb") as f:
                f.write(SNAPSHOT_MAGIC)
                f.write(binascii.unhexlify(snapshot_id))
        else:
            raise Exception("snapshot id not provided")

        # Create bind mounts directories
        bind_mounts_dir = os.path.join(client_dir, "bind-mounts")
        util.mkdir_p(bind_mounts_dir)
        for mount in client_config.bind_mounts:
            util.mkdir_p(os.path.join(bind_mounts_dir, mount))

        config_path = os.path.join(client_dir, MOUNT_CONFIG)
        self._save_client_config(client_config, config_path)

        # Prepare to mount
        mount_info = eden_ttypes.MountInfo(
            mountPoint=os.fsencode(path),
            edenClientPath=os.fsencode(client_dir))
        with self.get_thrift_client() as client:
            client.mount(mount_info)

        self._run_post_clone_hooks(path, client_dir, client_config)

        # Add mapping of mount path to client directory in config.json
        self._add_path_to_directory_map(path, os.path.basename(client_dir))
Exemple #7
0
    def create_test_mount(
        self,
        path: str,
        snapshot: Optional[str] = None,
        client_name: Optional[str] = None,
        scm_type: str = "hg",
        active: bool = True,
        setup_path: bool = True,
        dirstate_parent: Union[str, Tuple[str, str], None] = None,
        backing_repo: Optional[Path] = None,
    ) -> EdenCheckout:
        """
        Define a configured mount.

        If active is True and status was set to ALIVE when creating the FakeClient
        then the mount will appear as a normal active mount.  It will be reported in the
        thrift results and the mount table, and the mount directory will be populated
        with a .hg/ or .git/ subdirectory.

        The setup_path argument can be set to False to prevent creating the fake mount
        directory on disk.

        Returns the absolute path to the mount directory.
        """
        full_path = os.path.join(self._tmp_dir, path)
        if full_path in self._checkouts_by_path:
            raise Exception(f"duplicate mount definition: {full_path}")

        if snapshot is None:
            snapshot = self.default_commit_hash
        if client_name is None:
            client_name = path.replace("/", "_")
        backing_repo_path = (backing_repo if backing_repo is not None else
                             self.default_backing_repo)

        state_dir = self.clients_path / client_name
        assert full_path not in self._checkouts_by_path
        config = CheckoutConfig(
            backing_repo=backing_repo_path,
            scm_type=scm_type,
            guid=uuid.uuid4(),
            mount_protocol="prjfs" if sys.platform == "win32" else "fuse",
            default_revision=snapshot,
            redirections={},
            active_prefetch_profiles=[],
        )
        checkout = FakeCheckout(state_dir=state_dir,
                                config=config,
                                snapshot=snapshot)
        self._checkouts_by_path[full_path] = checkout

        # Write out the config file and snapshot file
        state_dir.mkdir()
        eden_checkout = EdenCheckout(typing.cast(EdenInstance, self),
                                     Path(full_path), state_dir)
        eden_checkout.save_config(config)
        eden_checkout.save_snapshot(snapshot)

        if active and self._status == fb303_status.ALIVE:
            # Report the mount in /proc/mounts
            dev_id = self._next_dev_id
            self._next_dev_id += 1
            self.mount_table.stats[full_path] = mtab.MTStat(
                st_uid=os.getuid(),
                st_dev=dev_id,
                st_mode=(stat.S_IFDIR | 0o755))

            # Tell the thrift client to report the mount as active
            self._fake_client._mounts.append(
                eden_ttypes.MountInfo(
                    mountPoint=os.fsencode(full_path),
                    edenClientPath=os.fsencode(state_dir),
                    state=eden_ttypes.MountState.RUNNING,
                ))

            # Set up directories on disk that look like the mounted checkout
            if setup_path:
                os.makedirs(full_path)
                if scm_type == "hg":
                    self._setup_hg_path(full_path, checkout, dirstate_parent)
                elif scm_type == "git":
                    os.mkdir(os.path.join(full_path, ".git"))

        return EdenCheckout(typing.cast(EdenInstance, self), Path(full_path),
                            Path(state_dir))
Exemple #8
0
    def clone(self, client_config: ClientConfig, path: str, snapshot_id: str):
        if path in self._get_directory_map():
            raise Exception('''\
mount path %s is already configured (see `eden list`). \
Do you want to run `eden mount %s` instead?''' % (path, path))

        # Make sure that path is a valid destination for the clone.
        st = None
        try:
            st = os.stat(path)
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                # Note that this could also throw if path is /a/b/c and /a
                # exists, but it is a file.
                util.mkdir_p(path)
            else:
                raise

        # Note that st will be None if `mkdir_p` was run in the catch block.
        if st:
            if stat.S_ISDIR(st.st_mode):
                # If an existing directory was specified, then verify it is
                # empty.
                if len(os.listdir(path)) > 0:
                    raise OSError(errno.ENOTEMPTY, os.strerror(errno.ENOTEMPTY),
                                  path)
            else:
                # Throw because it exists, but it is not a directory.
                raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)

        # Create client directory
        clients_dir = self._get_clients_dir()
        util.mkdir_p(clients_dir)  # This directory probably already exists.
        client_dir = self._create_client_dir_for_path(clients_dir, path)

        # Store snapshot ID
        if snapshot_id:
            client_snapshot = os.path.join(client_dir, SNAPSHOT)
            with open(client_snapshot, 'wb') as f:
                f.write(SNAPSHOT_MAGIC)
                f.write(binascii.unhexlify(snapshot_id))
        else:
            raise Exception('snapshot id not provided')

        # Create bind mounts directories
        bind_mounts_dir = os.path.join(client_dir, 'bind-mounts')
        util.mkdir_p(bind_mounts_dir)
        for mount in client_config.bind_mounts:
            util.mkdir_p(os.path.join(bind_mounts_dir, mount))

        config_path = os.path.join(client_dir, MOUNT_CONFIG)
        self._save_client_config(client_config, config_path)

        # Prepare to mount
        mount_info = eden_ttypes.MountInfo(mountPoint=path,
                                           edenClientPath=client_dir)
        with self.get_thrift_client() as client:
            client.mount(mount_info)

        self._run_post_clone_hooks(path, client_dir, client_config)

        # Add mapping of mount path to client directory in config.json
        self._add_path_to_directory_map(path, os.path.basename(client_dir))
Exemple #9
0
    def test_end_to_end_test_with_various_scenarios(self, mock_watchman):
        side_effects: List[Dict[str, Any]] = []
        calls = []
        tmp_dir = tempfile.mkdtemp(prefix='eden_test.')
        try:
            # In edenfs_path1, we will break the snapshot check.
            edenfs_path1 = os.path.join(tmp_dir, 'path1')
            # In edenfs_path2, we will break the inotify check and the Nuclide
            # subscriptions check.
            edenfs_path2 = os.path.join(tmp_dir, 'path2')

            calls.append(call(['watch-list']))
            side_effects.append({'roots': [edenfs_path1, edenfs_path2]})

            calls.append(call(['watch-project', edenfs_path1]))
            side_effects.append({'watcher': 'eden'})

            calls.append(call(['debug-get-subscriptions', edenfs_path1]))
            side_effects.append(
                _create_watchman_subscription(
                    filewatcher_subscription=f'filewatcher-{edenfs_path1}', ))

            calls.append(call(['watch-project', edenfs_path2]))
            side_effects.append({'watcher': 'inotify'})
            calls.append(call(['watch-del', edenfs_path2]))
            side_effects.append({'watch-del': True, 'root': edenfs_path2})
            calls.append(call(['watch-project', edenfs_path2]))
            side_effects.append({'watcher': 'eden'})

            calls.append(call(['debug-get-subscriptions', edenfs_path2]))
            side_effects.append(
                _create_watchman_subscription(filewatcher_subscription=None))

            mock_watchman.side_effect = side_effects

            out = io.StringIO()
            dry_run = False
            mount_paths = OrderedDict()
            edenfs_path1_snapshot_hex = 'abcd' * 10
            mount_paths[edenfs_path1] = {
                'bind-mounts': {},
                'mount': edenfs_path1,
                'scm_type': 'hg',
                'snapshot': edenfs_path1_snapshot_hex,
                'client-dir': '/I_DO_NOT_EXIST1'
            }
            mount_paths[edenfs_path2] = {
                'bind-mounts': {},
                'mount': edenfs_path2,
                'scm_type': 'git',
                'snapshot': 'dcba' * 10,
                'client-dir': '/I_DO_NOT_EXIST2'
            }
            config = FakeConfig(mount_paths, is_healthy=True)
            config.get_thrift_client()._mounts = [
                eden_ttypes.MountInfo(mountPoint=edenfs_path1),
                eden_ttypes.MountInfo(mountPoint=edenfs_path2),
            ]

            os.mkdir(edenfs_path1)
            hg_dir = os.path.join(edenfs_path1, '.hg')
            os.mkdir(hg_dir)
            dirstate = os.path.join(hg_dir, 'dirstate')
            dirstate_hash = b'\x12\x34\x56\x78' * 5
            parents = (dirstate_hash, b'\x00' * 20)
            with open(dirstate, 'wb') as f:
                eden.dirstate.write(f, parents, tuples_dict={}, copymap={})

            mount_table = FakeMountTable()
            mount_table.stats[edenfs_path1] = mtab.MTStat(st_uid=os.getuid(),
                                                          st_dev=11)
            mount_table.stats[edenfs_path2] = mtab.MTStat(st_uid=os.getuid(),
                                                          st_dev=12)
            exit_code = doctor.cure_what_ails_you(config, dry_run, out,
                                                  mount_table)
        finally:
            shutil.rmtree(tmp_dir)

        self.assertEqual(
            f'''\
Performing 3 checks for {edenfs_path1}.
p1 for {edenfs_path1} is {'12345678' * 5}, but Eden's internal
hash in its SNAPSHOT file is {edenfs_path1_snapshot_hex}.
Performing 2 checks for {edenfs_path2}.
Previous Watchman watcher for {edenfs_path2} was "inotify" but is now "eden".
Nuclide appears to be used to edit {edenfs_path2},
but a key Watchman subscription appears to be missing.
This can cause file changes to fail to show up in Nuclide.
Currently, the only workaround this is to run
"Nuclide Remote Projects: Kill And Restart" from the
command palette in Atom.
Number of fixes made: 1.
Number of issues that could not be fixed: 2.
''', out.getvalue())
        mock_watchman.assert_has_calls(calls)
        self.assertEqual(1, exit_code)