diff --git a/autoinstall-schema.json b/autoinstall-schema.json index 1e78394d..1503dcb2 100644 --- a/autoinstall-schema.json +++ b/autoinstall-schema.json @@ -721,6 +721,13 @@ "type": "string" } } + }, + "shutdown": { + "type": "string", + "enum": [ + "reboot", + "poweroff" + ] } }, "required": [ diff --git a/po/POTFILES.in b/po/POTFILES.in index 29032463..6efa40b2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -42,6 +42,7 @@ subiquity/common/filesystem/tests/test_actions.py subiquity/common/filesystem/tests/test_labels.py subiquity/common/filesystem/tests/test_manipulator.py subiquity/common/__init__.py +subiquity/common/resources.py subiquity/common/serialize.py subiquity/common/tests/__init__.py subiquity/common/tests/test_serialization.py @@ -72,8 +73,11 @@ subiquitycore/ssh.py subiquitycore/testing/__init__.py subiquitycore/testing/view_helpers.py subiquitycore/tests/__init__.py +subiquitycore/tests/mocks.py subiquitycore/tests/test_netplan.py +subiquitycore/tests/test_pubsub.py subiquitycore/tests/test_view.py +subiquitycore/tests/util.py subiquitycore/tuicontroller.py subiquitycore/tui.py subiquitycore/ui/actionmenu.py @@ -120,6 +124,7 @@ subiquity/models/tests/__init__.py subiquity/models/tests/test_filesystem.py subiquity/models/tests/test_mirror.py subiquity/models/tests/test_subiquity.py +subiquity/models/timezone.py subiquity/models/updates.py subiquity/server/controller.py subiquity/server/controllers/cmdlist.py @@ -135,21 +140,26 @@ subiquity/server/controllers/mirror.py subiquity/server/controllers/network.py subiquity/server/controllers/package.py subiquity/server/controllers/proxy.py -subiquity/server/controllers/reboot.py subiquity/server/controllers/refresh.py subiquity/server/controllers/reporting.py +subiquity/server/controllers/shutdown.py subiquity/server/controllers/snaplist.py subiquity/server/controllers/ssh.py subiquity/server/controllers/tests/test_keyboard.py +subiquity/server/controllers/timezone.py subiquity/server/controllers/updates.py subiquity/server/controllers/userdata.py subiquity/server/controllers/zdev.py subiquity/server/dryrun.py subiquity/server/errors.py +subiquity/server/geoip.py subiquity/server/__init__.py subiquity/server/server.py +subiquity/server/tests/__init__.py +subiquity/server/tests/test_geoip.py subiquity/tests/fakes.py subiquity/tests/__init__.py +subiquity/tests/test_timezonecontroller.py subiquity/ui/frame.py subiquity/ui/__init__.py subiquity/ui/mount.py diff --git a/subiquity/client/client.py b/subiquity/client/client.py index 0f8a6303..d513ce38 100644 --- a/subiquity/client/client.py +++ b/subiquity/client/client.py @@ -252,7 +252,7 @@ class SubiquityClient(TuiApplication): elif event['SUBIQUITY_EVENT_TYPE'] == 'finish': print('finish: ' + event["MESSAGE"]) context_name = event.get('SUBIQUITY_CONTEXT_NAME', '') - if context_name == 'subiquity/Reboot/reboot': + if context_name == 'subiquity/Shutdown/shutdown': self.exit() async def connect(self): diff --git a/subiquity/client/controllers/progress.py b/subiquity/client/controllers/progress.py index c3dbafe2..fba00777 100644 --- a/subiquity/client/controllers/progress.py +++ b/subiquity/client/controllers/progress.py @@ -21,7 +21,10 @@ import aiohttp from subiquitycore.context import with_context from subiquity.client.controller import SubiquityTuiController -from subiquity.common.types import ApplicationState +from subiquity.common.types import ( + ApplicationState, + ShutdownMode, + ) from subiquity.ui.views.installprogress import ( InstallRunning, ProgressView, @@ -65,7 +68,7 @@ class ProgressController(SubiquityTuiController): async def send_reboot_and_wait(self): try: - await self.app.client.reboot.POST() + await self.app.client.shutdown.POST(mode=ShutdownMode.REBOOT) except aiohttp.ClientError: pass self.app.exit() diff --git a/subiquity/common/apidef.py b/subiquity/common/apidef.py index 9f564ecd..3bacac94 100644 --- a/subiquity/common/apidef.py +++ b/subiquity/common/apidef.py @@ -37,6 +37,7 @@ from subiquity.common.types import ( IdentityData, NetworkStatus, RefreshStatus, + ShutdownMode, SnapInfo, SnapListResponse, SnapSelection, @@ -235,8 +236,8 @@ class API: def GET() -> TimeZoneInfo: ... def POST(tz: str): ... - class reboot: - def POST(): ... + class shutdown: + def POST(mode: ShutdownMode, immediate: bool = False): ... class LinkAction(enum.Enum): diff --git a/subiquity/common/types.py b/subiquity/common/types.py index 6bbc6250..8461b11d 100644 --- a/subiquity/common/types.py +++ b/subiquity/common/types.py @@ -330,3 +330,8 @@ class SnapListResponse: class TimeZoneInfo: timezone: str from_geoip: bool + + +class ShutdownMode(enum.Enum): + REBOOT = enum.auto() + POWEROFF = enum.auto() diff --git a/subiquity/server/controllers/__init__.py b/subiquity/server/controllers/__init__.py index a4498c11..3edbc7d4 100644 --- a/subiquity/server/controllers/__init__.py +++ b/subiquity/server/controllers/__init__.py @@ -25,9 +25,9 @@ from .mirror import MirrorController from .network import NetworkController from .package import PackageController from .proxy import ProxyController -from .reboot import RebootController from .refresh import RefreshController from .reporting import ReportingController +from .shutdown import ShutdownController from .snaplist import SnapListController from .ssh import SSHController from .timezone import TimeZoneController @@ -50,9 +50,9 @@ __all__ = [ 'NetworkController', 'PackageController', 'ProxyController', - 'RebootController', 'RefreshController', 'ReportingController', + 'ShutdownController', 'SnapListController', 'SSHController', 'TimeZoneController', diff --git a/subiquity/server/controllers/reboot.py b/subiquity/server/controllers/shutdown.py similarity index 54% rename from subiquity/server/controllers/reboot.py rename to subiquity/server/controllers/shutdown.py index fe68a075..e82732b1 100644 --- a/subiquity/server/controllers/reboot.py +++ b/subiquity/server/controllers/shutdown.py @@ -23,42 +23,68 @@ from subiquitycore.context import with_context from subiquitycore.utils import arun_command, run_command from subiquity.common.apidef import API +from subiquity.common.types import ShutdownMode from subiquity.server.controller import SubiquityController from subiquity.server.controllers.install import ApplicationState log = logging.getLogger("subiquity.controllers.restart") -class RebootController(SubiquityController): +class ShutdownController(SubiquityController): - endpoint = API.reboot + endpoint = API.shutdown + autoinstall_key = 'shutdown' + autoinstall_schema = { + 'type': 'string', + 'enum': ['reboot', 'poweroff'] + } def __init__(self, app): super().__init__(app) - self.user_reboot_event = asyncio.Event() - self.rebooting_event = asyncio.Event() + # user_shutdown_event is set when the user requests the shutdown. + # server_reboot_event is set when the server is ready for shutdown + # shuttingdown_event is set when the shutdown has begun (so don't + # depend on anything actually happening after it is set, it's all a bag + # of races from that point!) + self.user_shutdown_event = asyncio.Event() + self.server_reboot_event = asyncio.Event() + self.shuttingdown_event = asyncio.Event() + self.mode = ShutdownMode.REBOOT - async def POST(self): + def load_autoinstall_data(self, data): + if data == 'reboot': + self.mode = ShutdownMode.REBOOT + elif data == 'poweroff': + self.mode = ShutdownMode.POWEROFF + + async def POST(self, mode: ShutdownMode, immediate: bool = False): + self.mode = mode self.app.controllers.Install.stop_uu() - self.user_reboot_event.set() - await self.rebooting_event.wait() + self.user_shutdown_event.set() + if immediate: + self.server_reboot_event.set() + await self.shuttingdown_event.wait() def interactive(self): return self.app.interactive def start(self): + self.app.aio_loop.create_task(self._wait_install()) self.app.aio_loop.create_task(self._run()) - async def _run(self): - Install = self.app.controllers.Install - await Install.install_task + async def _wait_install(self): + await self.app.controllers.Install.install_task await self.app.controllers.Late.run_event.wait() await self.copy_logs_to_target() + self.server_reboot_event.set() + + async def _run(self): + await self.server_reboot_event.wait() if self.app.interactive: - await self.user_reboot_event.wait() - self.reboot() + await self.user_shutdown_event.wait() + self.shutdown() elif self.app.state == ApplicationState.DONE: - self.reboot() + self.shutdown() @with_context() async def copy_logs_to_target(self, context): @@ -80,12 +106,16 @@ class RebootController(SubiquityController): except Exception: log.exception("saving journal failed") - @with_context() - def reboot(self, context): - self.rebooting_event.set() + @with_context(description='mode={self.mode.name}') + def shutdown(self, context): + self.shuttingdown_event.set() if self.opts.dry_run: self.app.exit() else: - if platform.machine() == 's390x': - run_command(["chreipl", "/target/boot"]) - run_command(["/sbin/reboot"]) + if self.app.state == ApplicationState.DONE: + if platform.machine() == 's390x': + run_command(["chreipl", "/target/boot"]) + if self.mode == ShutdownMode.REBOOT: + run_command(["/sbin/reboot"]) + elif self.mode == ShutdownMode.POWEROFF: + run_command(["/sbin/poweroff"]) diff --git a/subiquity/server/server.py b/subiquity/server/server.py index e4743e7e..c4651e94 100644 --- a/subiquity/server/server.py +++ b/subiquity/server/server.py @@ -222,7 +222,7 @@ class SubiquityServer(Application): "Install", "Updates", "Late", - "Reboot", + "Shutdown", ] def make_model(self): diff --git a/subiquitycore/context.py b/subiquitycore/context.py index 0ed9c399..954ec041 100644 --- a/subiquitycore/context.py +++ b/subiquitycore/context.py @@ -131,7 +131,7 @@ def with_context(name=None, description="", **context_kw): context = self.context kw['context'] = context.child( name=name.format(**kw), - description=description.format(**kw), + description=description.format(self=self, **kw), **context_kw) return kw