Exemple #1
0
async def test_unmount_error(
    gstate: GlobalState,
    fs: fake_filesystem.FakeFilesystem,
    mocker: MockerFixture,
) -> None:
    async def mock_call(
        cmd: List[str],
    ) -> Tuple[int, Optional[str], Optional[str]]:
        raise Exception("Failed unmount.")

    mocker.patch(
        "gravel.controllers.nodes.systemdisk.aqr_run_cmd", new=mock_call
    )
    from gravel.controllers.nodes.systemdisk import MountError, SystemDisk

    systemdisk = SystemDisk(gstate)
    throws = False
    try:
        await systemdisk.unmount()
    except MountError as e:
        assert "does not exist" in e.message
        throws = True
    assert throws

    fs.create_dir("/var/lib/aquarium-system")
    throws = False
    try:
        await systemdisk.unmount()
    except MountError as e:
        assert "failed unmount" in e.message.lower()
        throws = True
    assert throws
def test_update_yaml(fs: FakeFilesystem) -> None:
    """
    Test ``update_yaml``.
    """
    path = Path("/path/to/blog/test.yaml")
    fs.create_dir(path.parent)

    # create new file
    with update_yaml(path) as config:
        config["foo"] = "bar"

    # check contents
    with update_yaml(path) as config:
        assert config["foo"] == "bar"

    # test error
    with open(path, "w", encoding="utf-8") as output:
        output.write("{{ test }}")
    with pytest.raises(yaml.constructor.ConstructorError) as excinfo:
        with update_yaml(path) as config:
            pass
    assert (str(excinfo.value) == """while constructing a mapping
  in "<unicode string>", line 1, column 1:
    {{ test }}
    ^
found unhashable key
  in "<unicode string>", line 1, column 2:
    {{ test }}
     ^""")
Exemple #3
0
def test_init_state_fail(gstate: GlobalState,
                         fs: fake_filesystem.FakeFilesystem) -> None:

    from gravel.controllers.nodes.mgr import NodeError

    if fs.exists("/etc/aquarium/node.json"):
        fs.remove("/etc/aquarium/node.json")

    nodemgr = NodeMgr(gstate)
    assert fs.exists("/etc/aquarium/node.json")
    for f in fs.listdir("/etc/aquarium"):
        fs.remove(f"/etc/aquarium/{f}")
    assert fs.exists("/etc/aquarium")
    fs.rmdir("/etc/aquarium")
    fs.create_dir("/etc/aquarium", perm_bits=0o500)

    throws = False
    try:
        nodemgr._init_state()
    except NodeError:
        throws = True
    assert throws

    # clean up
    for f in fs.listdir("/etc/aquarium"):
        fs.remove(f"/etc/aquarium/{f}")
    fs.rmdir("/etc/aquarium")
def test_find_modified_files(
    fs: FakeFilesystem,
    root: Path,
    config: Config,
) -> None:
    """
    Test ``find_modified_files``.
    """
    publisher = Publisher(root, config, "generic")

    fs.create_dir(root / "build/generic")
    fs.create_dir(root / "build/generic/subdir")
    with freeze_time("2021-01-01T00:00:00Z"):
        (root / "build/generic/one").touch()
    with freeze_time("2021-01-02T00:00:00Z"):
        (root / "build/generic/subdir/two").touch()

    since = datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
    modified_files = list(
        publisher.find_modified_files(force=False, since=since))
    assert modified_files == [Path("/path/to/blog/build/generic/subdir/two")]

    since = datetime(2021, 1, 2, 12, 0, 0, tzinfo=timezone.utc)
    modified_files = list(
        publisher.find_modified_files(force=False, since=since))
    assert modified_files == []

    since = datetime(2021, 1, 2, 12, 0, 0, tzinfo=timezone.utc)
    modified_files = list(
        publisher.find_modified_files(force=True, since=since))
    assert modified_files == [
        Path("/path/to/blog/build/generic/one"),
        Path("/path/to/blog/build/generic/subdir/two"),
    ]
    def init_fs(fs: FakeFilesystem) -> None:
        # Fake as Linux so that all slashes in these test are forward
        fs.os = OSType.LINUX
        fs.path_separator = "/"
        fs.is_windows_fs = False
        fs.is_macos = False

        # Pre-create the output path, whereas it was not created before running
        # the detail file test above.
        fs.create_dir(path.join(OUTPUT_DIRECTORY, RUN_NAME))  # type: ignore
def test_find_directory(fs: FakeFilesystem) -> None:
    """
    Test ``find_directory``.
    """
    fs.create_dir("/path/to/blog/posts/first/css")
    fs.create_file("/path/to/blog/nefelibata.yaml")

    path = find_directory(Path("/path/to/blog/posts/first/css"))
    assert path == Path("/path/to/blog")

    with pytest.raises(SystemExit) as excinfo:
        find_directory(Path("/path/to"))
    assert str(excinfo.value) == "No configuration found!"
Exemple #7
0
def mock_filesystem(fs: FakeFilesystem) -> FakeFilesystem:
    """A pytest fixture which mocks the filesystem before each test."""
    # The "fs" argument triggers pyfakefs' own pytest fixture to register
    # After pyfakefs has started all filesystem actions will happen on a fake in-memory filesystem

    # Create a fake home directory and set the cwd to an empty directory
    fs.create_dir(Path.home() / "testing")
    os.chdir(Path.home() / "testing")

    # Reset singletons so that fresh Path instances get created
    container.reset_singletons()

    return fs
Exemple #8
0
def cephadm_fs(fs: fake_filesystem.FakeFilesystem, ):
    """
    use pyfakefs to stub filesystem calls
    """
    uid = os.getuid()
    gid = os.getgid()

    with mock.patch('os.fchown'), \
         mock.patch('cephadm.extract_uid_gid', return_value=(uid, gid)):

        fs.create_dir(cd.DATA_DIR)
        fs.create_dir(cd.LOG_DIR)
        fs.create_dir(cd.LOCK_DIR)
        fs.create_dir(cd.LOGROTATE_DIR)
        fs.create_dir(cd.UNIT_DIR)

        yield fs
Exemple #9
0
def test_fail_ctor(gstate: GlobalState,
                   fs: fake_filesystem.FakeFilesystem) -> None:

    from gravel.controllers.nodes.mgr import NodeError

    if fs.exists("/etc/aquarium/node.json"):
        fs.remove("/etc/aquarium/node.json")
    fs.create_dir("/etc/aquarium/node.json")
    throws = False
    try:
        NodeMgr(gstate)
    except NodeError:
        throws = True
    except Exception:
        assert False
    assert throws
    # clean up
    fs.rmdir("/etc/aquarium/node.json")
Exemple #10
0
def root(fs: FakeFilesystem) -> Iterator[Path]:
    """
    Create the blog root directory.
    """
    # Add the templates to the fake filesystem, so builders can load them
    # during tests. The actual path depends if we're running in development
    # mode (``src/``) or installed (``site-packages``).
    root = get_project_root()
    locations = ("src/nefelibata/templates", "site-packages/nefelibata/templates")
    for location in locations:
        try:
            fs.add_real_directory(root / location)
        except FileNotFoundError:
            pass

    root = Path("/path/to/blog")
    fs.create_dir(root)
    yield root
Exemple #11
0
async def test_get_posts(fs: FakeFilesystem, root: Path,
                         config: Config) -> None:
    """
    Test ``get_posts``.
    """
    with freeze_time("2021-01-01T00:00:00Z"):
        fs.create_dir(root / "posts/one")
        fs.create_file(root / "posts/one/index.mkd")
    with freeze_time("2021-01-02T00:00:00Z"):
        fs.create_dir(root / "posts/two")
        fs.create_file(root / "posts/two/index.mkd")

    posts = get_posts(root, config)
    assert len(posts) == 2
    assert posts[0].path == Path(root / "posts/two/index.mkd")
    assert posts[1].path == Path(root / "posts/one/index.mkd")

    # test limited number of posts returned
    posts = get_posts(root, config, 1)
    assert len(posts) == 1
Exemple #12
0
def fake_filesystem(fs: FakeFilesystem) -> FakeFilesystem:
    """A pytest fixture which mocks the filesystem before each test."""
    # The "fs" argument triggers pyfakefs' own pytest fixture to register
    # After pyfakefs has started all filesystem actions will happen on a fake in-memory filesystem

    # Proxy access to certifi's certificate authority bundle to the real filesystem
    # This is required to be able to send HTTP requests using requests
    fs.add_real_file(certifi.where())

    # Proxy access to package data to the real filesystem
    fs.add_real_directory(os.path.join(os.path.dirname(__file__), "../lean/ssh"))

    # Create a fake home directory and set the cwd to an empty directory
    fs.create_dir(Path.home() / "testing")
    os.chdir(Path.home() / "testing")

    # Reset all singletons so Path instances get recreated
    # Path instances are bound to the filesystem that was active at the time of their creation
    # When the filesystem changes, old Path instances bound to previous filesystems may cause weird behavior
    container.reset_singletons()

    return fs
Exemple #13
0
def host_sysfs(fs: fake_filesystem.FakeFilesystem):
    """Create a fake filesystem to represent sysfs"""
    enc_path = '/sys/class/scsi_generic/sg2/device/enclosure/0:0:1:0'
    dev_path = '/sys/class/scsi_generic/sg2/device'
    slot_count = 12 
    fs.create_dir(dev_path)
    fs.create_file(os.path.join(dev_path, 'vendor'), contents="EnclosuresInc")
    fs.create_file(os.path.join(dev_path, 'model'), contents="D12")
    fs.create_file(os.path.join(enc_path, 'id'), contents='1')
    fs.create_file(os.path.join(enc_path, 'components'), contents=str(slot_count))
    for slot_num in range(slot_count):
        slot_dir = os.path.join(enc_path, str(slot_num))
        fs.create_file(os.path.join(slot_dir, 'locate'), contents='0')
        fs.create_file(os.path.join(slot_dir, 'fault'), contents='0')
        fs.create_file(os.path.join(slot_dir, 'slot'), contents=str(slot_num))
        if slot_num < 6:
            fs.create_file(os.path.join(slot_dir, 'status'), contents='Ok')
            slot_dev = os.path.join(slot_dir, 'device')
            fs.create_dir(slot_dev)
            fs.create_file(os.path.join(slot_dev, 'vpd_pg80'), contents=f'fake{slot_num:0>3}')
        else:
            fs.create_file(os.path.join(slot_dir, 'status'), contents='not installed')

    yield fs
Exemple #14
0
def dataset_dir(fs: FakeFilesystem):
    path = "/tmp/dataset"
    fs.create_dir(path)
    return path
Exemple #15
0
async def test_init(fs: fake_filesystem.FakeFilesystem) -> None:

    from gravel.controllers.deployment.mgr import (
        AlreadyInitedError,
        DeploymentMgr,
        InitError,
        InitStateEnum,
        NotPreInitedError,
    )

    mgr = DeploymentMgr()

    raised: bool = False
    try:
        await mgr.init()
    except NotPreInitedError:
        raised = True
    except Exception:
        assert False
    assert raised

    mgr._preinited = True
    mgr._inited = True
    raised = False
    try:
        await mgr.init()
    except AlreadyInitedError:
        raised = True
    except Exception:
        assert False
    assert raised

    mgr._preinited = True
    mgr._inited = False
    await mgr.init()  # succeeds because we're not installed yet.
    assert mgr._init_state < InitStateEnum.INSTALLED

    # test canonical case, we're installed but don't have a state yet.
    fs.reset()
    mgr = DeploymentMgr()
    mgr._preinited = True
    mgr._inited = False
    mgr._init_state = InitStateEnum.INSTALLED

    assert not fs.exists("/etc/aquarium")
    await mgr.init()
    assert mgr._inited
    assert fs.exists("/etc/aquarium/")
    assert fs.isdir("/etc/aquarium")
    assert fs.exists("/etc/aquarium/state.json")

    # ensure we have a malformed file in /etc/aquarium/state.json
    fs.reset()
    mgr = DeploymentMgr()
    mgr._preinited = True
    mgr._inited = False
    mgr._init_state = InitStateEnum.INSTALLED

    fs.create_dir("/etc/aquarium")
    with open("/etc/aquarium/state.json", "w") as f:
        f.write("foobarbaz")

    raised = False
    try:
        await mgr.init()
    except InitError:
        raised = True
    except Exception:
        assert False
    assert raised

    # now we have something in state.json that is not a file
    fs.reset()
    mgr = DeploymentMgr()
    mgr._preinited = True
    mgr._inited = False
    mgr._init_state = InitStateEnum.INSTALLED

    fs.create_dir("/etc/aquarium/state.json")
    raised = False
    try:
        await mgr.init()
    except InitError:
        raised = True
    except Exception:
        assert False
    assert raised
Exemple #16
0
async def aquarium_startup(
    get_data_contents: Callable[[str, str], str],
    mocker: MockerFixture,
    fs: fake_filesystem.FakeFilesystem,
):
    # Need the following to fake up KV
    mock_ceph_modules(mocker)
    fs.create_dir("/var/lib/aquarium")

    async def startup(aquarium_app: FastAPI, aquarium_api: FastAPI):
        from fastapi.logger import logger as fastapi_logger

        from gravel.cephadm.cephadm import Cephadm
        from gravel.controllers.inventory.inventory import Inventory
        from gravel.controllers.nodes.deployment import NodeDeployment
        from gravel.controllers.nodes.errors import NodeCantDeployError
        from gravel.controllers.nodes.mgr import (
            NodeError,
            NodeInitStage,
            NodeMgr,
        )
        from gravel.controllers.orch.ceph import Ceph, Mgr, Mon
        from gravel.controllers.resources.devices import Devices
        from gravel.controllers.resources.status import Status
        from gravel.controllers.resources.storage import Storage

        logger: logging.Logger = fastapi_logger

        class FakeNodeDeployment(NodeDeployment):
            # Do we still need this thing since removing etcd?
            pass

        class FakeNodeMgr(NodeMgr):
            def __init__(self, gstate: GlobalState):
                super().__init__(gstate)
                self._deployment = FakeNodeDeployment(gstate, self._connmgr)

            async def start(self) -> None:
                assert self._state
                logger.debug(f"start > {self._state}")

                if not self.deployment_state.can_start():
                    raise NodeError("unable to start unstartable node")

                assert self._init_stage == NodeInitStage.NONE

                if self.deployment_state.nostage:
                    await self._node_prepare()
                else:
                    assert (self.deployment_state.ready
                            or self.deployment_state.deployed)
                    assert self._state.hostname
                    assert self._state.address
                    await self.gstate.store.ensure_connection()

            async def _obtain_images(self) -> bool:
                return True

        class FakeCephadm(Cephadm):
            def __init__(self):
                super().__init__(ContainersOptionsModel())

            async def call(
                self,
                cmd: List[str],
                noimage: bool = False,
                outcb: Optional[Callable[[str], None]] = None,
            ) -> Tuple[str, str, int]:
                # Implement expected calls to cephadm with testable responses
                if cmd[0] == "pull":
                    return "", "", 0
                elif cmd[0] == "gather-facts":
                    return (
                        get_data_contents(DATA_DIR, "gather_facts_real.json"),
                        "",
                        0,
                    )
                elif cmd == ["ceph-volume", "inventory", "--format", "json"]:
                    return (
                        get_data_contents(DATA_DIR, "inventory_real.json"),
                        "",
                        0,
                    )
                else:
                    print(cmd)
                    print(outcb)
                    raise Exception("Tests should not get here")

        class FakeCeph(Ceph):
            def __init__(self, conf_file: str = "/etc/ceph/ceph.conf"):
                self.conf_file = conf_file
                self._is_connected = False

            def connect(self):
                if not self.is_connected():
                    self.cluster = mocker.Mock()
                    self._is_connected = True

        class FakeStorage(Storage):  # type: ignore
            available = 2000  # type: ignore
            total = 2000  # type: ignore

        gstate: GlobalState = GlobalState(FakeKV)

        # init node mgr
        nodemgr: NodeMgr = FakeNodeMgr(gstate)

        # Prep cephadm
        cephadm: Cephadm = FakeCephadm()
        gstate.add_cephadm(cephadm)

        # Set up Ceph connections
        ceph: Ceph = FakeCeph()
        ceph_mgr: Mgr = Mgr(ceph)
        gstate.add_ceph_mgr(ceph_mgr)
        ceph_mon: Mon = Mon(ceph)
        gstate.add_ceph_mon(ceph_mon)

        # Set up all of the tickers
        devices: Devices = Devices(
            gstate.config.options.devices.probe_interval,
            nodemgr,
            ceph_mgr,
            ceph_mon,
        )
        gstate.add_devices(devices)

        status: Status = Status(gstate.config.options.status.probe_interval,
                                gstate, nodemgr)
        gstate.add_status(status)

        inventory: Inventory = Inventory(
            gstate.config.options.inventory.probe_interval, nodemgr, gstate)
        gstate.add_inventory(inventory)

        storage: Storage = FakeStorage(
            gstate.config.options.storage.probe_interval, nodemgr, ceph_mon)
        gstate.add_storage(storage)

        await nodemgr.start()
        await gstate.start()

        # Add instances into FastAPI's state:
        aquarium_api.state.gstate = gstate
        aquarium_api.state.nodemgr = nodemgr

    yield startup
def test_get_enclosures(mocker: MockerFixture, fs: FakeFilesystem, root: Path) -> None:
    """
    Test ``get_enclosures``.
    """
    MP3 = mocker.patch("nefelibata.enclosure.MP3")
    metadata = {
        "TIT2": "A title",
        "TPE1": "An artist",
        "TALB": "An album",
        "TDRC": 2021,
        "TRCK": 1,
    }
    MP3.return_value.get.side_effect = metadata.get
    MP3.return_value.info.length = 123.0

    piexif = mocker.patch("nefelibata.enclosure.piexif")
    piexif.ImageIFD.ImageDescription = 270
    piexif.load.return_value = {"0th": {270: "This is a nice photo"}}

    fs.create_dir(root / "posts/first")
    path = root / "posts/first/index.mkd"

    # create supported files
    (root / "posts/first/song.mp3").touch()
    (root / "posts/first/photo.jpg").touch()
    (root / "posts/first/logo.png").touch()

    # create non-supported file
    (root / "posts/first/test.txt").touch()

    enclosures = get_enclosures(root, path.parent)
    assert len(enclosures) == 3

    assert enclosures[0].dict() == {
        "path": Path("/path/to/blog/posts/first/song.mp3"),
        "description": '"A title" (2m3s) by An artist (An album, 2021)',
        "type": "audio/mpeg",
        "length": 0,
        "href": "first/song.mp3",
        "title": "A title",
        "artist": "An artist",
        "album": "An album",
        "year": 2021,
        "duration": 123.0,
        "track": 0,
    }

    assert enclosures[1].dict() == {
        "description": "This is a nice photo",
        "href": "first/photo.jpg",
        "length": 0,
        "path": Path("/path/to/blog/posts/first/photo.jpg"),
        "type": "image/jpeg",
    }

    assert enclosures[2].dict() == {
        "description": "Image logo.png",
        "href": "first/logo.png",
        "length": 0,
        "path": Path("/path/to/blog/posts/first/logo.png"),
        "type": "image/png",
    }
Exemple #18
0
async def test_enable(
    gstate: GlobalState,
    fs: fake_filesystem.FakeFilesystem,
    mocker: MockerFixture,
) -> None:

    from gravel.controllers.nodes.systemdisk import (
        MountError,
        OverlayError,
        SystemDisk,
    )

    async def mount_fail() -> None:
        raise MountError("Failed mount.")

    overlayed_paths = []
    bindmounts = []

    async def mock_call(
        cmd: List[str],
    ) -> Tuple[int, Optional[str], Optional[str]]:

        if cmd[2] == "overlay":
            assert "lower" in cmd[4]
            lowerstr = (cmd[4].split(","))[0]
            lower = (lowerstr.split("="))[1]
            overlayed_paths.append(lower)
        elif cmd[1] == "--bind":
            assert len(cmd) == 4
            bindmounts.append(cmd[3])
        else:
            raise Exception(f"Unknown call: {cmd}")
        return 0, None, None

    # ensure we don't have a mounted fs
    fs.add_real_file(
        source_path=os.path.join(DATA_DIR, "mounts_without_aquarium.raw"),
        target_path="/proc/mounts",
    )

    systemdisk = SystemDisk(gstate)
    assert not systemdisk.mounted
    systemdisk.mount = mount_fail

    mocker.patch(
        "gravel.controllers.nodes.systemdisk.aqr_run_cmd", new=mock_call
    )

    throws = False
    try:
        await systemdisk.enable()
    except OverlayError as e:
        assert "failed mount" in e.message.lower()
        throws = True
    assert throws

    systemdisk.mount = mocker.AsyncMock()

    for upper in systemdisk._overlaydirs.keys():
        fs.create_dir(f"/var/lib/aquarium-system/{upper}/overlay")
        fs.create_dir(f"/var/lib/aquarium-system/{upper}/temp")

    for ours in systemdisk._bindmounts.keys():
        fs.create_dir(f"/var/lib/aquarium-system/{ours}")

    await systemdisk.enable()

    for lower in systemdisk._overlaydirs.values():
        assert fs.exists(lower)
        assert lower in overlayed_paths

    for theirs in systemdisk._bindmounts.values():
        assert fs.exists(theirs)
        assert theirs in bindmounts
Exemple #19
0
async def test_network(
    mocker: MockerFixture,
    fs: fake_filesystem.FakeFilesystem,
) -> None:
    async def mock_restart_network(
        cmd: List[str], ) -> Tuple[int, Optional[str], Optional[str]]:
        assert cmd == ["systemctl", "restart", "network.service"]
        return 0, None, None

    mocker.patch(
        "gravel.controllers.resources.network.aqr_run_cmd",
        new=mock_restart_network,
    )

    from gravel.controllers.resources.network import (
        InterfaceConfigModel,
        InterfaceModel,
        Network,
        RouteModel,
    )

    fs.create_dir(
        "/sys/devices/pci0000:00/0000:00:02.0/0000:01:00.0/net/enp1s0")
    fs.create_symlink(
        "/sys/class/net/enp1s0/device",
        "/sys/devices/pci0000:00/0000:00:02.0/0000:01:00.0/net/enp1s0",
    )
    fs.create_dir("/sys/class/net/virbr0")

    fs.add_real_file(
        source_path=os.path.join(DATA_DIR, "ifcfg-eth0"),
        target_path="/etc/sysconfig/network/ifcfg-eth0",
    )
    fs.create_file("/etc/sysconfig/network/ifcfg-eth0~")
    fs.create_file("/etc/sysconfig/network/ifcfg-eth0.rpmsave")
    fs.create_file("/etc/sysconfig/network/ifcfg-lo")
    fs.add_real_file(
        source_path=os.path.join(DATA_DIR, "config"),
        target_path="/etc/sysconfig/network/config",
    )

    network = Network(5.0)
    assert await network._should_tick()

    await network._do_tick()

    ifaces = network.interfaces
    assert len(ifaces) > 0

    assert "eth0" in ifaces
    assert ifaces["eth0"].name == "eth0"
    assert ifaces["eth0"].config.bootproto == "dhcp"
    assert ifaces["eth0"].config.ipaddr == ""
    assert ifaces["eth0"].config.bonding_slaves == []

    assert "eth0~" not in ifaces
    assert "eth0.rpmsave" not in ifaces

    assert "enp1s0" in ifaces
    assert ifaces["enp1s0"].config is None

    assert "virbr0" not in ifaces

    assert "lo" not in ifaces

    assert network.nameservers == []

    assert network.routes == []

    ifaces["bond0"] = InterfaceModel(
        name="bond0",
        config=InterfaceConfigModel(
            bootproto="static",
            ipaddr="192.168.121.10/24",
            bonding_slaves=["enp1s0", "enp2s0"],
        ),
    )
    await network.apply_config(
        ifaces,
        ["8.8.8.8", "1.1.1.1"],
        routes=[
            RouteModel(destination="default", gateway="192.168.121.1"),
            RouteModel(destination="192.168.0.0/16", interface="bond0"),
        ],
    )

    ifaces = network.interfaces
    assert "bond0" in ifaces
    assert os.path.exists("/etc/sysconfig/network/ifcfg-enp1s0")
    assert os.path.exists("/etc/sysconfig/network/ifcfg-enp2s0")
    assert network.nameservers == ["8.8.8.8", "1.1.1.1"]
    assert os.path.exists("/etc/sysconfig/network/routes")
    assert os.path.exists("/etc/sysconfig/network/ifroute-bond0")
    assert len(network.routes) == 2
Exemple #20
0
async def aquarium_startup(
    get_data_contents: Callable[[str, str], str],
    mocker: MockerFixture,
    fs: fake_filesystem.FakeFilesystem,
):
    # Need the following to fake up KV
    mock_ceph_modules(mocker)
    fs.create_dir("/var/lib/aquarium")

    async def startup(aquarium_app: FastAPI, aquarium_api: FastAPI):
        from fastapi.logger import logger as fastapi_logger

        from gravel.cephadm.cephadm import Cephadm
        from gravel.controllers.ceph.ceph import Ceph, Mgr, Mon
        from gravel.controllers.config import Config
        from gravel.controllers.inventory.inventory import Inventory
        from gravel.controllers.nodes.mgr import NodeInitStage, NodeMgr
        from gravel.controllers.resources.devices import Devices
        from gravel.controllers.resources.network import Network
        from gravel.controllers.resources.status import Status
        from gravel.controllers.resources.storage import Storage

        logger: logging.Logger = fastapi_logger

        class FakeNodeMgr(NodeMgr):
            def __init__(self, gstate: GlobalState):
                super().__init__(gstate)

            async def start(self) -> None:
                assert self._state
                logger.debug(f"start > {self._state}")

                assert self._init_stage == NodeInitStage.INITED
                self._init_stage = NodeInitStage.AVAILABLE

            async def _obtain_images(self) -> bool:
                return True

        class FakeCephadm(Cephadm):
            def __init__(self):
                super().__init__()

            async def call(
                self,
                cmd: List[str],
                noimage: bool = False,
                outcb: Optional[Callable[[str], None]] = None,
            ) -> Tuple[str, str, int]:
                # Implement expected calls to cephadm with testable responses
                if cmd[0] == "pull":
                    return "", "", 0
                elif cmd[0] == "gather-facts":
                    return (
                        get_data_contents(DATA_DIR, "gather_facts_real.json"),
                        "",
                        0,
                    )
                elif cmd == ["ceph-volume", "inventory", "--format", "json"]:
                    return (
                        get_data_contents(DATA_DIR, "inventory_real.json"),
                        "",
                        0,
                    )
                else:
                    print(cmd)
                    print(outcb)
                    raise Exception("Tests should not get here")

        class FakeCeph(Ceph):
            def __init__(self, conf_file: str = "/etc/ceph/ceph.conf"):
                self.conf_file = conf_file
                self._is_connected = False

            def connect(self):
                if not self.is_connected():
                    self.cluster = mocker.Mock()
                    self._is_connected = True

        class FakeStorage(Storage):  # type: ignore
            available = 2000  # type: ignore
            total = 2000  # type: ignore

        config = Config()
        kvstore = FakeKV()
        gstate: GlobalState = GlobalState(config, kvstore)
        config.init()
        kvstore.init()

        # init node mgr
        nodemgr: NodeMgr = FakeNodeMgr(gstate)
        nodemgr.init()

        # Prep cephadm
        cephadm: Cephadm = FakeCephadm()
        gstate.add_cephadm(cephadm)
        cephadm.set_config(gstate.config.options.containers)

        # Set up Ceph connections
        ceph: Ceph = FakeCeph()
        ceph_mgr: Mgr = Mgr(ceph)
        gstate.add_ceph_mgr(ceph_mgr)
        ceph_mon: Mon = Mon(ceph)
        gstate.add_ceph_mon(ceph_mon)

        # Set up all of the tickers
        devices: Devices = Devices(
            gstate.config.options.devices.probe_interval,
            nodemgr,
            ceph_mgr,
            ceph_mon,
        )
        gstate.add_devices(devices)

        status: Status = Status(gstate.config.options.status.probe_interval,
                                gstate, nodemgr)
        gstate.add_status(status)

        inventory: Inventory = Inventory(
            gstate.config.options.inventory.probe_interval, nodemgr, gstate)
        gstate.add_inventory(inventory)

        storage: Storage = FakeStorage(
            gstate.config.options.storage.probe_interval, nodemgr, ceph_mon)
        gstate.add_storage(storage)

        network: Network = Network(
            gstate.config.options.network.probe_interval)
        gstate.add_network(network)

        await nodemgr.start()
        await gstate.start()

        # Add instances into FastAPI's state:
        aquarium_api.state.gstate = gstate
        aquarium_api.state.nodemgr = nodemgr

    yield startup