move refresh controller to new world

This commit is contained in:
Michael Hudson-Doyle 2020-10-09 16:09:48 +13:00
parent 7faa1634ff
commit aac00dad6c
10 changed files with 145 additions and 69 deletions

View File

@ -3,6 +3,7 @@ subiquity/client/client.py
subiquity/client/controller.py subiquity/client/controller.py
subiquity/client/controllers/__init__.py subiquity/client/controllers/__init__.py
subiquity/client/controllers/progress.py subiquity/client/controllers/progress.py
subiquity/client/controllers/refresh.py
subiquity/client/controllers/welcome.py subiquity/client/controllers/welcome.py
subiquity/client/__init__.py subiquity/client/__init__.py
subiquity/client/keycodes.py subiquity/client/keycodes.py
@ -35,7 +36,6 @@ subiquity/controllers/mirror.py
subiquity/controllers/network.py subiquity/controllers/network.py
subiquity/controllers/proxy.py subiquity/controllers/proxy.py
subiquity/controllers/reboot.py subiquity/controllers/reboot.py
subiquity/controllers/refresh.py
subiquity/controllers/snaplist.py subiquity/controllers/snaplist.py
subiquity/controllers/ssh.py subiquity/controllers/ssh.py
subiquity/controllers/tests/__init__.py subiquity/controllers/tests/__init__.py
@ -122,6 +122,7 @@ subiquity/server/controllers/__init__.py
subiquity/server/controllers/install.py subiquity/server/controllers/install.py
subiquity/server/controllers/locale.py subiquity/server/controllers/locale.py
subiquity/server/controllers/package.py subiquity/server/controllers/package.py
subiquity/server/controllers/refresh.py
subiquity/server/controllers/reporting.py subiquity/server/controllers/reporting.py
subiquity/server/controllers/userdata.py subiquity/server/controllers/userdata.py
subiquity/server/dryrun.py subiquity/server/dryrun.py

View File

@ -91,6 +91,7 @@ class SubiquityClient(TuiApplication):
controllers = [ controllers = [
"Welcome", "Welcome",
"Refresh",
"Progress", "Progress",
] ]

View File

@ -14,9 +14,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from .progress import ProgressController from .progress import ProgressController
from .refresh import RefreshController
from .welcome import WelcomeController from .welcome import WelcomeController
__all__ = [ __all__ = [
'ProgressController', 'ProgressController',
'RefreshController',
'WelcomeController', 'WelcomeController',
] ]

View File

@ -0,0 +1,90 @@
# Copyright 2019 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import asyncio
import logging
import aiohttp
from subiquitycore.tuicontroller import (
Skip,
)
from subiquity.common.types import (
RefreshCheckState,
)
from subiquity.client.controller import (
SubiquityTuiController,
)
from subiquity.ui.views.refresh import RefreshView
log = logging.getLogger('subiquity.client.controllers.refresh')
class RefreshController(SubiquityTuiController):
endpoint_name = 'refresh'
def __init__(self, app):
super().__init__(app)
self.offered_first_time = False
async def get_progress(self, change):
while True:
try:
return await self.endpoint.progress.GET(change_id=change)
except aiohttp.ClientError:
# Probably the server is restarting.
await asyncio.sleep(1)
async def make_ui(self, index=1):
if self.app.updated:
raise Skip()
show = False
self.status = await self.endpoint.GET()
if index == 1:
if self.status.availability == RefreshCheckState.AVAILABLE:
show = True
self.offered_first_time = True
elif index == 2:
if not self.offered_first_time:
if self.status.availability in [RefreshCheckState.UNKNOWN,
RefreshCheckState.AVAILABLE]:
show = True
else:
raise AssertionError("unexpected index {}".format(index))
if show:
return RefreshView(self)
else:
raise Skip()
async def wait_for_check(self):
self.status = await self.endpoint.GET(wait=True)
return self.status
async def start_update(self):
return await self.endpoint.POST()
def run_answers(self):
# Handled in the view
pass
def done(self, sender=None):
log.debug("RefreshController.done next_screen")
self.app.next_screen()
def cancel(self, sender=None):
self.app.prev_screen()

View File

@ -22,6 +22,7 @@ from subiquity.common.types import (
ErrorReportRef, ErrorReportRef,
InstallState, InstallState,
InstallStatus, InstallStatus,
RefreshStatus,
) )
@ -59,6 +60,18 @@ class API:
def GET() -> None: def GET() -> None:
"""Requests to this method will fail with a HTTP 500.""" """Requests to this method will fail with a HTTP 500."""
class refresh:
def GET(wait: bool = False) -> RefreshStatus:
"""Get information about the snap refresh status.
If wait is true, block until the status is known."""
def POST() -> int:
"""Start the update and return the change id."""
class progress:
def GET(change_id: int) -> dict: ...
class install: class install:
class status: class status:
def GET(cur: Optional[InstallState] = None) -> InstallStatus: ... def GET(cur: Optional[InstallState] = None) -> InstallStatus: ...

View File

@ -21,7 +21,6 @@ from .mirror import MirrorController
from .network import NetworkController from .network import NetworkController
from .proxy import ProxyController from .proxy import ProxyController
from .reboot import RebootController from .reboot import RebootController
from .refresh import RefreshController
from .snaplist import SnapListController from .snaplist import SnapListController
from .ssh import SSHController from .ssh import SSHController
from .zdev import ZdevController from .zdev import ZdevController
@ -34,7 +33,6 @@ __all__ = [
'NetworkController', 'NetworkController',
'ProxyController', 'ProxyController',
'RebootController', 'RebootController',
'RefreshController',
'RepeatedController', 'RepeatedController',
'SnapListController', 'SnapListController',
'SSHController', 'SSHController',

View File

@ -18,6 +18,7 @@ from .debconf import DebconfController
from .install import InstallController from .install import InstallController
from .locale import LocaleController from .locale import LocaleController
from .package import PackageController from .package import PackageController
from .refresh import RefreshController
from .reporting import ReportingController from .reporting import ReportingController
from .userdata import UserdataController from .userdata import UserdataController
@ -29,6 +30,7 @@ __all__ = [
'LateController', 'LateController',
'LocaleController', 'LocaleController',
'PackageController', 'PackageController',
'RefreshController',
'ReportingController', 'ReportingController',
'UserdataController', 'UserdataController',
] ]

View File

@ -24,24 +24,23 @@ from subiquitycore.async_helpers import (
SingleInstanceTask, SingleInstanceTask,
) )
from subiquitycore.context import with_context from subiquitycore.context import with_context
from subiquitycore.tuicontroller import (
Skip,
)
from subiquity.common.apidef import API
from subiquity.common.types import ( from subiquity.common.types import (
RefreshCheckState, RefreshCheckState,
RefreshStatus, RefreshStatus,
) )
from subiquity.controller import ( from subiquity.server.controller import (
SubiquityTuiController, SubiquityController,
) )
from subiquity.ui.views.refresh import RefreshView
log = logging.getLogger('subiquity.controllers.refresh') log = logging.getLogger('subiquity.server.controllers.refresh')
class RefreshController(SubiquityTuiController): class RefreshController(SubiquityController):
endpoint = API.refresh
autoinstall_key = "refresh-installer" autoinstall_key = "refresh-installer"
autoinstall_schema = { autoinstall_schema = {
@ -58,23 +57,24 @@ class RefreshController(SubiquityTuiController):
] ]
def __init__(self, app): def __init__(self, app):
self.ai_data = {}
super().__init__(app) super().__init__(app)
self.ai_data = {}
self.snap_name = os.environ.get("SNAP_NAME", "subiquity") self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
self.configure_task = None self.configure_task = None
self.check_task = None self.check_task = None
self.status = RefreshStatus(availability=RefreshCheckState.UNKNOWN) self.status = RefreshStatus(availability=RefreshCheckState.UNKNOWN)
self.offered_first_time = False
if 'update' in self.ai_data:
self.active = self.ai_data['update']
else:
self.active = self.interactive()
def load_autoinstall_data(self, data): def load_autoinstall_data(self, data):
if data is not None: if data is not None:
self.ai_data = data self.ai_data = data
@property
def active(self):
if 'update' in self.ai_data:
return True
else:
return self.interactive()
def start(self): def start(self):
if not self.active: if not self.active:
return return
@ -95,16 +95,9 @@ class RefreshController(SubiquityTuiController):
return return
change_id = await self.start_update(context=context) change_id = await self.start_update(context=context)
while True: while True:
try:
change = await self.get_progress(change_id) change = await self.get_progress(change_id)
except requests.exceptions.RequestException as e: if change['status'] not in ['Do', 'Doing', 'Done']:
raise e raise Exception("update failed: %s", change['status'])
if change['status'] == 'Done':
# Clearly if we got here we didn't get restarted by
# snapd/systemctl (dry-run mode or logged in via SSH)
self.app.restart(remove_last_screen=False)
if change['status'] not in ['Do', 'Doing']:
raise Exception("update failed")
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
@with_context() @with_context()
@ -137,8 +130,6 @@ class RefreshController(SubiquityTuiController):
def get_refresh_channel(self): def get_refresh_channel(self):
"""Return the channel we should refresh subiquity to.""" """Return the channel we should refresh subiquity to."""
if 'channel' in self.answers:
return self.answers['channel']
prefix = "subiquity-channel=" prefix = "subiquity-channel="
for arg in self.app.kernel_cmdline: for arg in self.app.kernel_cmdline:
if arg.startswith(prefix): if arg.startswith(prefix):
@ -209,39 +200,20 @@ class RefreshController(SubiquityTuiController):
async def get_progress(self, change): async def get_progress(self, change):
result = await self.app.snapd.get('v2/changes/{}'.format(change)) result = await self.app.snapd.get('v2/changes/{}'.format(change))
return result['result'] change = result['result']
if change['status'] == 'Done':
# Clearly if we got here we didn't get restarted by
# snapd/systemctl (dry-run mode)
self.app.restart()
return change
def make_ui(self, index=1): async def GET(self, wait: bool = False) -> RefreshStatus:
if self.app.updated: if wait:
raise Skip() await self.check_task.wait()
show = False
if index == 1:
if self.status.availability == RefreshCheckState.AVAILABLE:
show = True
self.offered_first_time = True
elif index == 2:
if not self.offered_first_time:
if self.status.availability in [RefreshCheckState.UNKNOWN,
RefreshCheckState.AVAILABLE]:
show = True
else:
raise AssertionError("unexpected index {}".format(index))
if show:
return RefreshView(self)
else:
raise Skip()
async def wait_for_check(self):
await self.check_task.task
return self.status return self.status
def run_answers(self): async def POST(self, context) -> int:
# Handled in the view return await self.start_update(context=context)
pass
def done(self, sender=None): async def progress_GET(self, change_id: int) -> dict:
log.debug("RefreshController.done next_screen") return await self.get_progress(change_id)
self.app.next_screen()
def cancel(self, sender=None):
self.app.prev_screen()

View File

@ -119,6 +119,7 @@ class SubiquityServer(Application):
"Package", "Package",
"Debconf", "Debconf",
"Locale", "Locale",
"Refresh",
"Install", "Install",
"Late", "Late",
] ]

View File

@ -17,7 +17,7 @@ import asyncio
import json import json
import logging import logging
import requests import aiohttp
from urwid import ( from urwid import (
ProgressBar, ProgressBar,
@ -246,15 +246,11 @@ class RefreshView(BaseView):
async def _update(self): async def _update(self):
try: try:
change_id = await self.controller.start_update() change_id = await self.controller.start_update()
except requests.exceptions.RequestException as e: except aiohttp.ClientError as e:
self.update_failed(exc_message(e)) self.update_failed(exc_message(e))
return return
while True: while True:
try:
change = await self.controller.get_progress(change_id) change = await self.controller.get_progress(change_id)
except requests.exceptions.RequestException as e:
self.update_failed(exc_message(e))
return
if change['status'] == 'Done': if change['status'] == 'Done':
# Clearly if we got here we didn't get restarted by # Clearly if we got here we didn't get restarted by
# snapd/systemctl (dry-run mode or logged in via SSH) # snapd/systemctl (dry-run mode or logged in via SSH)