async def test_deploy_checks(gstate: GlobalState, nodemgr: NodeMgr) -> None: from gravel.controllers.nodes.deployment import NodeStageEnum from gravel.controllers.nodes.mgr import ( DeployParamsModel, NodeCantDeployError, NodeInitStage, NodeNotStartedError, ) nodemgr._init_stage = NodeInitStage.NONE throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr") ) except NodeNotStartedError: throws = True assert throws nodemgr._init_stage = NodeInitStage.PREPARE throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr") ) except NodeNotStartedError: throws = True assert throws nodemgr._init_stage = NodeInitStage.AVAILABLE nodemgr._deployment._state._stage = NodeStageEnum.ERROR throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr") ) except NodeCantDeployError: throws = True assert throws nodemgr._deployment._state._stage = NodeStageEnum.NONE throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="", ntpaddr="my.ntp.addr") ) except NodeCantDeployError as e: assert e.message == "missing hostname parameter" throws = True assert throws throws = False try: await nodemgr.deploy(DeployParamsModel(hostname="barbaz", ntpaddr="")) except NodeCantDeployError as e: assert e.message == "missing ntp server address" throws = True assert throws
async def test_deploy_check_disk_solution(gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr) -> None: from gravel.controllers.nodes.disks import DiskSolution from gravel.controllers.nodes.mgr import NodeCantDeployError, NodeInitStage nodemgr._init_stage = NodeInitStage.AVAILABLE def empty_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution() def fail_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution(possible=True) mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution", new=empty_solution) throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr")) except NodeCantDeployError as e: assert e.message == "No possible deployment solution found." throws = True assert throws mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution", new=fail_solution) throws = False try: await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr")) except AssertionError: throws = True assert throws
async def test_join_checks(gstate: GlobalState) -> None: from gravel.controllers.nodes.mgr import ( JoinParamsModel, NodeCantJoinError, NodeError, NodeInitStage, NodeNotStartedError, ) nodemgr = NodeMgr(gstate) throws = False nodemgr._init_stage = NodeInitStage.NONE try: await nodemgr.join( "1.2.3.4", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar") ) except NodeNotStartedError: throws = True assert throws throws = False nodemgr._init_stage = NodeInitStage.STARTED try: await nodemgr.join( "1.2.3.4", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar") ) except NodeCantJoinError: throws = True assert throws throws = False nodemgr._init_stage = NodeInitStage.AVAILABLE try: await nodemgr.join( "1.2.3.4", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="") ) except NodeError as e: throws = True assert "hostname" in e.message assert throws
async def test_bootstrap_finisher_cb(gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr) -> None: from gravel.controllers.nodes.mgr import NodeInitStage nodemgr._init_stage = NodeInitStage.NONE assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None)) nodemgr._init_stage = NodeInitStage.PREPARE assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None)) nodemgr._init_stage = NodeInitStage.STARTED assert await expect_assertion(nodemgr._post_bootstrap_finisher(True, None)) nodemgr._init_stage = NodeInitStage.AVAILABLE nodemgr._save_state = mocker.AsyncMock() nodemgr._post_bootstrap_config = mocker.AsyncMock() await nodemgr._post_bootstrap_finisher(True, None) nodemgr._save_state.assert_called_once() # type: ignore nodemgr._post_bootstrap_config.assert_called_once() # type: ignore
async def test_finish_deployment_cb(gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr) -> None: from gravel.controllers.nodes.mgr import NodeInitStage nodemgr._init_stage = NodeInitStage.NONE assert await expect_assertion(nodemgr._finish_deployment(True, None)) nodemgr._init_stage = NodeInitStage.PREPARE assert await expect_assertion(nodemgr._finish_deployment(True, None)) nodemgr._init_stage = NodeInitStage.STARTED assert await expect_assertion(nodemgr._finish_deployment(True, None)) nodemgr._init_stage = NodeInitStage.AVAILABLE nodemgr._deployment.finish_deployment = mocker.MagicMock() nodemgr._load = mocker.AsyncMock() nodemgr._node_start = mocker.AsyncMock() await nodemgr._finish_deployment(True, None) nodemgr._deployment.finish_deployment.assert_called_once() # type: ignore nodemgr._load.assert_called_once() # type: ignore nodemgr._node_start.assert_called_once() # type: ignore
def test_node_shutdown(gstate: GlobalState, mocker: MockerFixture) -> None: from gravel.controllers.nodes.mgr import NodeInitStage, NodeMgr class FakeTask: called = False def cancel(self) -> None: self.called = True nodemgr = NodeMgr(gstate) nodemgr._incoming_task = FakeTask() # type: ignore nodemgr._init_stage = NodeInitStage.NONE nodemgr._node_shutdown() assert nodemgr._init_stage == NodeInitStage.STOPPING assert nodemgr._incoming_task.called
async def test_join_check_disk_solution( gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr ) -> None: from gravel.controllers.nodes.disks import DiskSolution from gravel.controllers.nodes.mgr import ( JoinParamsModel, NodeCantJoinError, NodeInitStage, ) nodemgr._init_stage = NodeInitStage.AVAILABLE def empty_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution() mocker.patch( "gravel.controllers.nodes.disks.Disks.gen_solution", new=empty_solution ) throws = False try: await nodemgr.join( "1.2.3.4", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar") ) except NodeCantJoinError as e: assert e.message == "no disk deployment solution found" throws = True assert throws def fail_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution(possible=True) mocker.patch( "gravel.controllers.nodes.disks.Disks.gen_solution", new=fail_solution ) throws = False try: await nodemgr.join( "1.2.3.4", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar") ) except AssertionError: throws = True assert throws
async def test_deploy(gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr) -> None: from gravel.controllers.auth import UserMgr, UserModel from gravel.controllers.inventory.disks import DiskDevice from gravel.controllers.nodes.deployment import DeploymentConfig from gravel.controllers.nodes.disks import DiskSolution from gravel.controllers.nodes.mgr import NodeInitStage called_mock_deploy = False def mock_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution( systemdisk=DiskDevice( id="foo01", name="foo", path="/dev/foo", product="Foo", vendor="Foo Inc", size=1000, rotational=False, available=True, rejected_reasons=[], ), storage=[ DiskDevice( id="bar01", name="bar", path="/dev/bar", product="Bar", vendor="Bar LLC", size=2000, rotational=False, available=True, rejected_reasons=[], ), DiskDevice( id="baz01", name="baz", path="/dev/baz", product="Baz", vendor="Baz Ltd", size=2000, rotational=False, available=True, rejected_reasons=[], ), ], storage_size=4000, possible=True, ) async def mock_deploy( config: DeploymentConfig, post_bootstrap_cb: Callable[[bool, Optional[str]], Awaitable[None]], finisher: Callable[[bool, Optional[str]], Awaitable[None]], ) -> None: import inspect nonlocal called_mock_deploy called_mock_deploy = True assert config.hostname == "barbaz" assert config.address == "1.2.3.4" assert config.token == "751b-51fd-10d7-f7b4" assert config.ntp_addr == "my.ntp.addr" assert config.disks.system == "/dev/foo" assert len(config.disks.storage) == 2 assert "/dev/bar" in config.disks.storage assert "/dev/baz" in config.disks.storage assert post_bootstrap_cb is not None assert finisher is not None assert inspect.iscoroutinefunction(post_bootstrap_cb) assert inspect.iscoroutinefunction(finisher) mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution", new=mock_solution) nodemgr._init_stage = NodeInitStage.AVAILABLE nodemgr._generate_token = mocker.MagicMock( return_value="751b-51fd-10d7-f7b4") nodemgr._save_token = mocker.AsyncMock() nodemgr._deployment.deploy = mock_deploy await gstate.store.ensure_connection() await nodemgr.deploy( DeployParamsModel(hostname="barbaz", ntpaddr="my.ntp.addr")) assert called_mock_deploy assert nodemgr._token == "751b-51fd-10d7-f7b4" assert nodemgr._state.hostname == "barbaz" nodemgr._save_token.assert_called_once() # type: ignore ntpaddr = await gstate.store.get("/nodes/ntp_addr") assert ntpaddr == "my.ntp.addr" usermgr = UserMgr(gstate.store) assert await usermgr.exists("admin") user: Optional[UserModel] = await usermgr.get("admin") assert user is not None assert user.username == "admin" # We can't test the plain password here because it will fail # and we don't care particularly about the password itself, just that # the user has been populated. We'll leave for the 'UserMgr' tests to # validate the correctness of its operations. assert len(user.password) > 0
async def test_join(gstate: GlobalState, mocker: MockerFixture, nodemgr: NodeMgr) -> None: from uuid import UUID from gravel.controllers.inventory.disks import DiskDevice from gravel.controllers.nodes.deployment import DeploymentDisksConfig from gravel.controllers.nodes.disks import DiskSolution from gravel.controllers.nodes.mgr import JoinParamsModel, NodeInitStage def mock_solution(gstate: GlobalState) -> DiskSolution: return DiskSolution( systemdisk=DiskDevice( id="foo01", name="foo", path="/dev/foo", product="Foo", vendor="Foo Inc", size=1000, rotational=False, available=True, rejected_reasons=[], ), storage=[ DiskDevice( id="bar01", name="bar", path="/dev/bar", product="Bar", vendor="Bar LLC", size=2000, rotational=False, available=True, rejected_reasons=[], ), DiskDevice( id="baz01", name="baz", path="/dev/baz", product="Baz", vendor="Baz Ltd", size=2000, rotational=False, available=True, rejected_reasons=[], ), ], storage_size=4000, possible=True, ) async def mock_join( leader_address: str, token: str, uuid: UUID, hostname: str, address: str, disks: DeploymentDisksConfig, ) -> bool: assert leader_address == "10.1.2.3" assert token == "751b-51fd-10d7-f7b4" assert str(uuid) == "bba35d93-d4a5-48b3-804b-99c406555c89" assert hostname == "foobar" assert address == "1.2.3.4" assert disks.system == "/dev/foo" assert len(disks.storage) == 2 assert "/dev/bar" in disks.storage assert "/dev/baz" in disks.storage return True mocker.patch("gravel.controllers.nodes.disks.Disks.gen_solution", new=mock_solution) nodemgr._init_stage = NodeInitStage.AVAILABLE nodemgr._deployment.join = mocker.AsyncMock(side_effects=Exception()) throws = False try: await nodemgr.join( "10.1.2.3", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar"), ) except Exception: throws = True assert throws nodemgr._deployment.join.assert_called_once() # type: ignore nodemgr._deployment.join = mocker.AsyncMock(return_value=False) res = await nodemgr.join("10.1.2.3", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar")) assert not res nodemgr._deployment.join.assert_called_once() # type: ignore nodemgr._save_state = mocker.AsyncMock() nodemgr._node_start = mocker.AsyncMock() nodemgr._deployment.join = mock_join res = await nodemgr.join("10.1.2.3", "751b-51fd-10d7-f7b4", JoinParamsModel(hostname="foobar")) assert res assert nodemgr._token == "751b-51fd-10d7-f7b4" nodemgr._save_state.assert_called_once() # type: ignore nodemgr._node_start.assert_called_once() # type: ignore