Merge pull request #749 from mwhudson/better-nested-contexts
make it easier to nest contexts appropriately
This commit is contained in:
commit
9ea34e8b0d
|
@ -43,9 +43,9 @@ python3 scripts/check-yaml-fields.py .subiquity/subiquity-curtin-install.conf \
|
|||
debconf_selections.subiquity='"eek"'
|
||||
python3 scripts/check-yaml-fields.py .subiquity/var/lib/cloud/seed/nocloud-net/user-data \
|
||||
locale='"en_UK.UTF-8"'
|
||||
grep -q 'finish: subiquity/InstallProgress/postinstall/install_package1: SUCCESS: installing package1' \
|
||||
grep -q 'finish: subiquity/InstallProgress/install/postinstall/install_package1: SUCCESS: installing package1' \
|
||||
.subiquity/subiquity-debug.log
|
||||
grep -q 'finish: subiquity/InstallProgress/postinstall/install_package2: SUCCESS: installing package2' \
|
||||
grep -q 'finish: subiquity/InstallProgress/install/postinstall/install_package2: SUCCESS: installing package2' \
|
||||
.subiquity/subiquity-debug.log
|
||||
grep -q 'switching subiquity to edge' .subiquity/subiquity-debug.log
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import logging
|
|||
|
||||
import jsonschema
|
||||
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.controller import (
|
||||
BaseController,
|
||||
RepeatedController,
|
||||
|
@ -57,7 +58,8 @@ class SubiquityController(BaseController):
|
|||
"""
|
||||
pass
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
"""Apply autoinstall configuration.
|
||||
|
||||
This is only called for a non-interactive controller. It should
|
||||
|
@ -110,7 +112,7 @@ class RepeatedController(RepeatedController):
|
|||
self.autoinstall_applied = False
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
await self.orig.apply_autoinstall_config(self.index)
|
||||
await self.orig.apply_autoinstall_config(index=self.index)
|
||||
|
||||
def configured(self):
|
||||
self.orig.configured()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.utils import arun_command
|
||||
|
||||
from subiquity.controller import NoUIController
|
||||
|
@ -34,7 +35,8 @@ class CmdListController(NoUIController):
|
|||
def load_autoinstall_data(self, data):
|
||||
self.cmds = data
|
||||
|
||||
async def run(self):
|
||||
@with_context()
|
||||
async def run(self, context):
|
||||
for i, cmd in enumerate(self.cmds):
|
||||
with self.context.child("command_{}".format(i), cmd):
|
||||
if isinstance(cmd, str):
|
||||
|
@ -54,5 +56,6 @@ class LateController(CmdListController):
|
|||
|
||||
autoinstall_key = 'late-commands'
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
await self.run()
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
await self.run(context)
|
||||
|
|
|
@ -26,6 +26,7 @@ from subiquitycore.async_helpers import (
|
|||
schedule_task,
|
||||
SingleInstanceTask,
|
||||
)
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.lsb_release import lsb_release
|
||||
from subiquitycore.utils import (
|
||||
run_command,
|
||||
|
@ -102,7 +103,8 @@ class FilesystemController(SubiquityController):
|
|||
log.debug("self.ai_data = %s", data)
|
||||
self.ai_data = data
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
self.stop_listening_udev()
|
||||
await self._start_task
|
||||
await self._probe_task.wait()
|
||||
|
|
|
@ -17,6 +17,8 @@ import logging
|
|||
|
||||
import attr
|
||||
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
from subiquity.ui.views import IdentityView
|
||||
|
||||
|
@ -42,7 +44,8 @@ class IdentityController(SubiquityController):
|
|||
if data is not None:
|
||||
self.model.add_user(data)
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
if not self.model.user:
|
||||
if 'user-data' not in self.app.autoinstall_config:
|
||||
raise Exception("no identity data provided")
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
|
@ -38,7 +37,7 @@ from subiquitycore.async_helpers import (
|
|||
run_in_thread,
|
||||
schedule_task,
|
||||
)
|
||||
from subiquitycore.context import Status
|
||||
from subiquitycore.context import Status, with_context
|
||||
from subiquitycore.utils import (
|
||||
arun_command,
|
||||
astart_command,
|
||||
|
@ -79,25 +78,13 @@ class TracebackExtractor:
|
|||
self.traceback.append(line)
|
||||
|
||||
|
||||
def install_step(label, level=None, childlevel=None):
|
||||
def decorate(meth):
|
||||
name = meth.__name__
|
||||
|
||||
async def decorated(self, context, *args):
|
||||
manager = self.install_context(
|
||||
context, name, label, level, childlevel)
|
||||
with manager as subcontext:
|
||||
await meth(self, subcontext, *args)
|
||||
return decorated
|
||||
return decorate
|
||||
|
||||
|
||||
class InstallProgressController(SubiquityController):
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.model = app.base_model
|
||||
self.progress_view = ProgressView(self)
|
||||
app.add_event_listener(self)
|
||||
self.install_state = InstallState.NOT_STARTED
|
||||
|
||||
self.reboot_clicked = asyncio.Event()
|
||||
|
@ -116,12 +103,38 @@ class InstallProgressController(SubiquityController):
|
|||
return self.app.interactive()
|
||||
|
||||
def start(self):
|
||||
self.install_task = schedule_task(self.install(self.context))
|
||||
self.install_task = schedule_task(self.install())
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
await self.install_task
|
||||
self.app.reboot_on_exit = True
|
||||
|
||||
def _push_to_progress(self, context):
|
||||
if not self.app.interactive():
|
||||
return False
|
||||
if context.get('hidden', False):
|
||||
return False
|
||||
controller = context.get('controller')
|
||||
if controller is None or controller.interactive():
|
||||
return False
|
||||
return True
|
||||
|
||||
def report_start_event(self, context, description):
|
||||
if self._push_to_progress(context):
|
||||
msg = context.full_name()
|
||||
if description:
|
||||
msg += ': ' + description
|
||||
self.progress_view.event_start(context, msg)
|
||||
if context.get('is-install-context'):
|
||||
self.progress_view.event_start(context, context.description)
|
||||
|
||||
def report_finish_event(self, context, description, status):
|
||||
if self._push_to_progress(context):
|
||||
self.progress_view.event_finish(context)
|
||||
if context.get('is-install-context'):
|
||||
self.progress_view.event_finish(context)
|
||||
|
||||
def tpath(self, *path):
|
||||
return os.path.join(self.model.target, *path)
|
||||
|
||||
|
@ -152,17 +165,6 @@ class InstallProgressController(SubiquityController):
|
|||
elif event['SYSLOG_IDENTIFIER'] == self._log_syslog_identifier:
|
||||
self.curtin_log(event)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def install_context(self, context, name, description,
|
||||
level=None, childlevel=None):
|
||||
subcontext = context.child(name, description, level, childlevel)
|
||||
self.progress_view.event_start(subcontext, description)
|
||||
try:
|
||||
with subcontext:
|
||||
yield subcontext
|
||||
finally:
|
||||
self.progress_view.event_finish(subcontext)
|
||||
|
||||
def curtin_event(self, event):
|
||||
e = {
|
||||
"EVENT_TYPE": "???",
|
||||
|
@ -190,13 +192,11 @@ class InstallProgressController(SubiquityController):
|
|||
break
|
||||
if curtin_ctx:
|
||||
curtin_ctx.enter()
|
||||
self.progress_view.event_start(curtin_ctx, e["MESSAGE"])
|
||||
if event_type == 'finish':
|
||||
status = getattr(Status, e["RESULT"], Status.WARN)
|
||||
curtin_ctx = self.curtin_event_contexts.pop(e["NAME"], None)
|
||||
if curtin_ctx is not None:
|
||||
curtin_ctx.exit(status)
|
||||
self.progress_view.event_finish(curtin_ctx)
|
||||
|
||||
def curtin_log(self, event):
|
||||
log_line = event['MESSAGE']
|
||||
|
@ -240,7 +240,7 @@ class InstallProgressController(SubiquityController):
|
|||
|
||||
return curtin_cmd
|
||||
|
||||
@install_step("umounting /target dir")
|
||||
@with_context(description="umounting /target dir")
|
||||
async def unmount_target(self, context, target):
|
||||
cmd = [
|
||||
sys.executable, '-m', 'curtin', 'unmount',
|
||||
|
@ -252,7 +252,8 @@ class InstallProgressController(SubiquityController):
|
|||
if not self.opts.dry_run:
|
||||
shutil.rmtree(target)
|
||||
|
||||
@install_step("installing system", level="INFO", childlevel="DEBUG")
|
||||
@with_context(
|
||||
description="installing system", level="INFO", childlevel="DEBUG")
|
||||
async def curtin_install(self, context):
|
||||
log.debug('curtin_install')
|
||||
self.install_state = InstallState.RUNNING
|
||||
|
@ -286,7 +287,9 @@ class InstallProgressController(SubiquityController):
|
|||
def cancel(self):
|
||||
pass
|
||||
|
||||
@with_context()
|
||||
async def install(self, context):
|
||||
context.set('is-install-context', True)
|
||||
try:
|
||||
await asyncio.wait(
|
||||
{e.wait() for e in self.model.install_events})
|
||||
|
@ -331,8 +334,9 @@ class InstallProgressController(SubiquityController):
|
|||
log.debug("waited %s seconds for events to drain", waited)
|
||||
self.curtin_event_contexts.pop('', None)
|
||||
|
||||
@install_step(
|
||||
"final system configuration", level="INFO", childlevel="DEBUG")
|
||||
@with_context(
|
||||
description="final system configuration", level="INFO",
|
||||
childlevel="DEBUG")
|
||||
async def postinstall(self, context):
|
||||
autoinstall_path = os.path.join(
|
||||
self.app.root, 'var/log/installer/autoinstall-user-data')
|
||||
|
@ -345,15 +349,14 @@ class InstallProgressController(SubiquityController):
|
|||
packages = ['openssh-server']
|
||||
packages.extend(self.app.base_model.packages)
|
||||
for package in packages:
|
||||
subcontext = self.install_context(
|
||||
context,
|
||||
subcontext = context.child(
|
||||
"install_{}".format(package),
|
||||
"installing {}".format(package))
|
||||
with subcontext:
|
||||
await self.install_package(package)
|
||||
await self.restore_apt_config(context)
|
||||
|
||||
@install_step("configuring cloud-init")
|
||||
@with_context(description="configuring cloud-init")
|
||||
async def configure_cloud_init(self, context):
|
||||
await run_in_thread(self.model.configure_cloud_init)
|
||||
|
||||
|
@ -368,7 +371,7 @@ class InstallProgressController(SubiquityController):
|
|||
]
|
||||
await arun_command(self.logged_command(cmd), check=True)
|
||||
|
||||
@install_step("restoring apt configuration")
|
||||
@with_context(description="restoring apt configuration")
|
||||
async def restore_apt_config(self, context):
|
||||
if self.opts.dry_run:
|
||||
cmds = [["sleep", str(1/self.app.scale_factor)]]
|
||||
|
@ -386,7 +389,7 @@ class InstallProgressController(SubiquityController):
|
|||
for cmd in cmds:
|
||||
await arun_command(self.logged_command(cmd), check=True)
|
||||
|
||||
@install_step("downloading and installing security updates")
|
||||
@with_context(description="downloading and installing security updates")
|
||||
async def run_unattended_upgrades(self, context):
|
||||
target_tmp = os.path.join(self.model.target, "tmp")
|
||||
os.makedirs(target_tmp, exist_ok=True)
|
||||
|
@ -414,8 +417,7 @@ class InstallProgressController(SubiquityController):
|
|||
|
||||
async def stop_unattended_upgrades(self):
|
||||
self.progress_view.event_finish(self.unattended_upgrades_ctx)
|
||||
with self.install_context(
|
||||
self.unattended_upgrades_ctx.parent,
|
||||
with self.unattended_upgrades_ctx.parent.child(
|
||||
"stop_unattended_upgrades",
|
||||
"cancelling update"):
|
||||
if self.opts.dry_run:
|
||||
|
|
|
@ -18,6 +18,7 @@ import logging
|
|||
import attr
|
||||
|
||||
from subiquitycore.async_helpers import schedule_task
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
from subiquity.models.keyboard import KeyboardSetting
|
||||
|
@ -47,7 +48,8 @@ class KeyboardController(SubiquityController):
|
|||
if data is not None:
|
||||
self.model.setting = KeyboardSetting(**data)
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
await self.model.set_keyboard(self.model.setting)
|
||||
|
||||
def language_selected(self, code):
|
||||
|
|
|
@ -25,6 +25,7 @@ from subiquitycore.async_helpers import (
|
|||
run_in_thread,
|
||||
SingleInstanceTask,
|
||||
)
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
from subiquity.ui.views.mirror import MirrorView
|
||||
|
@ -73,7 +74,8 @@ class MirrorController(SubiquityController):
|
|||
merge_config(self.model.config, data)
|
||||
self.geoip_enabled = geoip and self.model.is_default()
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
if not self.geoip_enabled:
|
||||
return
|
||||
try:
|
||||
|
|
|
@ -17,6 +17,7 @@ import asyncio
|
|||
import logging
|
||||
|
||||
from subiquitycore.async_helpers import schedule_task
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.controllers.network import NetworkController
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
|
@ -110,11 +111,13 @@ class NetworkController(NetworkController, SubiquityController):
|
|||
async def delay(self):
|
||||
await asyncio.sleep(10)
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
if self.ai_data is None:
|
||||
await self.initial_delay
|
||||
with context.child("initial_delay"):
|
||||
await self.initial_delay
|
||||
self.update_initial_configs()
|
||||
self.apply_config()
|
||||
self.apply_config(context)
|
||||
await self.apply_config_task.wait()
|
||||
self.model.has_network = bool(
|
||||
self.network_event_receiver.default_routes)
|
||||
|
@ -129,9 +132,9 @@ class NetworkController(NetworkController, SubiquityController):
|
|||
return {'network': r}
|
||||
return super().render_config()
|
||||
|
||||
async def _apply_config(self, silent):
|
||||
async def _apply_config(self, context=None, *, silent):
|
||||
try:
|
||||
await super()._apply_config(silent)
|
||||
await super()._apply_config(context, silent=silent)
|
||||
except asyncio.CancelledError:
|
||||
# asyncio.CancelledError is a subclass of Exception in
|
||||
# Python 3.6 (sadface)
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
from subiquity.ui.views.proxy import ProxyView
|
||||
|
||||
|
@ -40,7 +42,8 @@ class ProxyController(SubiquityController):
|
|||
self.model.proxy
|
||||
self.signal.emit_signal('network-proxy-set')
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
# XXX want to wait until signal sent by .start() has been seen
|
||||
# by everything; don't have a way to do that today.
|
||||
pass
|
||||
|
|
|
@ -19,6 +19,7 @@ import platform
|
|||
import subprocess
|
||||
|
||||
from subiquitycore.async_helpers import schedule_task
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.utils import arun_command, run_command
|
||||
|
||||
from subiquity.controller import SubiquityController
|
||||
|
@ -63,7 +64,8 @@ class RebootController(SubiquityController):
|
|||
run_command(["chreipl", "/target/boot"])
|
||||
run_command(["/sbin/reboot"])
|
||||
|
||||
async def apply_autoinstall_config(self):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
await self.copy_logs_to_target()
|
||||
self.reboot()
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ from subiquitycore.async_helpers import (
|
|||
schedule_task,
|
||||
SingleInstanceTask,
|
||||
)
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.controller import (
|
||||
Skip,
|
||||
)
|
||||
|
@ -86,7 +87,8 @@ class RefreshController(SubiquityController):
|
|||
self.check_for_update, propagate_errors=False)
|
||||
self.check_task.start_sync()
|
||||
|
||||
async def apply_autoinstall_config(self, index=1):
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context, index=1):
|
||||
if not self.active:
|
||||
return
|
||||
try:
|
||||
|
@ -95,7 +97,7 @@ class RefreshController(SubiquityController):
|
|||
return
|
||||
if self.check_state != CheckState.AVAILABLE:
|
||||
return
|
||||
change_id = await self.start_update()
|
||||
change_id = await self.start_update(context)
|
||||
while True:
|
||||
try:
|
||||
change = await self.get_progress(change_id)
|
||||
|
@ -120,33 +122,33 @@ class RefreshController(SubiquityController):
|
|||
return CheckState.UNAVAILABLE
|
||||
return task.result()
|
||||
|
||||
async def configure_snapd(self):
|
||||
with self.context.child("configure_snapd") as context:
|
||||
with context.child("get_details") as subcontext:
|
||||
try:
|
||||
r = await self.app.snapd.get(
|
||||
'v2/snaps/{snap_name}'.format(
|
||||
snap_name=self.snap_name))
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception("getting snap details")
|
||||
return
|
||||
self.current_snap_version = r['result']['version']
|
||||
for k in 'channel', 'revision', 'version':
|
||||
self.app.note_data_for_apport(
|
||||
"Snap" + k.title(), r['result'][k])
|
||||
subcontext.description = "current version of snap is: %r" % (
|
||||
self.current_snap_version)
|
||||
channel = self.get_refresh_channel()
|
||||
desc = "switching {} to {}".format(self.snap_name, channel)
|
||||
with context.child("switching", desc) as subcontext:
|
||||
try:
|
||||
await self.app.snapd.post_and_wait(
|
||||
'v2/snaps/{}'.format(self.snap_name),
|
||||
{'action': 'switch', 'channel': channel})
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception("switching channels")
|
||||
return
|
||||
subcontext.description = "switched to " + channel
|
||||
@with_context()
|
||||
async def configure_snapd(self, context):
|
||||
with context.child("get_details") as subcontext:
|
||||
try:
|
||||
r = await self.app.snapd.get(
|
||||
'v2/snaps/{snap_name}'.format(
|
||||
snap_name=self.snap_name))
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception("getting snap details")
|
||||
return
|
||||
self.current_snap_version = r['result']['version']
|
||||
for k in 'channel', 'revision', 'version':
|
||||
self.app.note_data_for_apport(
|
||||
"Snap" + k.title(), r['result'][k])
|
||||
subcontext.description = "current version of snap is: %r" % (
|
||||
self.current_snap_version)
|
||||
channel = self.get_refresh_channel()
|
||||
desc = "switching {} to {}".format(self.snap_name, channel)
|
||||
with context.child("switching", desc) as subcontext:
|
||||
try:
|
||||
await self.app.snapd.post_and_wait(
|
||||
'v2/snaps/{}'.format(self.snap_name),
|
||||
{'action': 'switch', 'channel': channel})
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception("switching channels")
|
||||
return
|
||||
subcontext.description = "switched to " + channel
|
||||
|
||||
def get_refresh_channel(self):
|
||||
"""Return the channel we should refresh subiquity to."""
|
||||
|
@ -183,35 +185,33 @@ class RefreshController(SubiquityController):
|
|||
if self.check_state == CheckState.UNKNOWN:
|
||||
self.check_task.start_sync()
|
||||
|
||||
async def check_for_update(self):
|
||||
@with_context()
|
||||
async def check_for_update(self, context):
|
||||
await asyncio.shield(self.configure_task)
|
||||
with self.context.child("check_for_update") as context:
|
||||
if self.app.updated:
|
||||
context.description = (
|
||||
"not offered update when already updated")
|
||||
return CheckState.UNAVAILABLE
|
||||
result = await self.app.snapd.get('v2/find', select='refresh')
|
||||
log.debug("check_for_update received %s", result)
|
||||
for snap in result["result"]:
|
||||
if snap["name"] == self.snap_name:
|
||||
self.new_snap_version = snap["version"]
|
||||
context.description = (
|
||||
"new version of snap available: %r"
|
||||
% self.new_snap_version)
|
||||
return CheckState.AVAILABLE
|
||||
else:
|
||||
context.description = (
|
||||
"no new version of snap available")
|
||||
if self.app.updated:
|
||||
context.description = "not offered update when already updated"
|
||||
return CheckState.UNAVAILABLE
|
||||
result = await self.app.snapd.get('v2/find', select='refresh')
|
||||
log.debug("check_for_update received %s", result)
|
||||
for snap in result["result"]:
|
||||
if snap["name"] == self.snap_name:
|
||||
self.new_snap_version = snap["version"]
|
||||
context.description = (
|
||||
"new version of snap available: %r"
|
||||
% self.new_snap_version)
|
||||
return CheckState.AVAILABLE
|
||||
else:
|
||||
context.description = "no new version of snap available"
|
||||
return CheckState.UNAVAILABLE
|
||||
|
||||
async def start_update(self):
|
||||
@with_context()
|
||||
async def start_update(self, context):
|
||||
open(self.app.state_path('updating'), 'w').close()
|
||||
with self.context.child("starting_update") as context:
|
||||
change = await self.app.snapd.post(
|
||||
'v2/snaps/{}'.format(self.snap_name),
|
||||
{'action': 'refresh'})
|
||||
context.description = "change id: {}".format(change)
|
||||
return change
|
||||
change = await self.app.snapd.post(
|
||||
'v2/snaps/{}'.format(self.snap_name),
|
||||
{'action': 'refresh'})
|
||||
context.description = "change id: {}".format(change)
|
||||
return change
|
||||
|
||||
async def get_progress(self, change):
|
||||
result = await self.app.snapd.get('v2/changes/{}'.format(change))
|
||||
|
|
|
@ -64,6 +64,7 @@ class ReportingController(NoUIController):
|
|||
def __init__(self, app):
|
||||
self.config = copy.deepcopy(INITIAL_CONFIG)
|
||||
super().__init__(app)
|
||||
app.add_event_listener(self)
|
||||
|
||||
def load_autoinstall_data(self, data):
|
||||
if self.app.interactive():
|
||||
|
@ -75,9 +76,11 @@ class ReportingController(NoUIController):
|
|||
def start(self):
|
||||
update_configuration(self.config)
|
||||
|
||||
def report_start_event(self, name, description, level):
|
||||
report_start_event(name, description, level=level)
|
||||
def report_start_event(self, context, description):
|
||||
report_start_event(
|
||||
context.full_name(), description, level=context.level)
|
||||
|
||||
def report_finish_event(self, name, description, result, level):
|
||||
def report_finish_event(self, context, description, result):
|
||||
result = getattr(status, result.name, status.WARN)
|
||||
report_finish_event(name, description, result, level=level)
|
||||
report_finish_event(
|
||||
context.full_name(), description, result, level=context.level)
|
||||
|
|
|
@ -128,6 +128,7 @@ class Subiquity(Application):
|
|||
self.journal_fd, self.journal_watcher = journald_listener(
|
||||
["subiquity"], self.subiquity_event, seek=True)
|
||||
super().__init__(opts)
|
||||
self.event_listeners = []
|
||||
self.install_lock_file = Lockfile(self.state_path("installing"))
|
||||
self.global_overlays = []
|
||||
self.block_log_dir = block_log_dir
|
||||
|
@ -288,41 +289,16 @@ class Subiquity(Application):
|
|||
traceback.print_exc()
|
||||
signal.pause()
|
||||
|
||||
def _push_to_progress(self, context):
|
||||
if not self.interactive():
|
||||
return False
|
||||
InstallProgress = getattr(self.controllers, "InstallProgress", None)
|
||||
if InstallProgress is None:
|
||||
return False
|
||||
if context.get('hidden', False):
|
||||
return False
|
||||
controller = context.get('controller')
|
||||
if controller is None or controller.interactive():
|
||||
return False
|
||||
return True
|
||||
def add_event_listener(self, listener):
|
||||
self.event_listeners.append(listener)
|
||||
|
||||
def report_start_event(self, context, description):
|
||||
# report_start_event gets called when the Reporting controller
|
||||
# is being loaded...
|
||||
Reporting = getattr(self.controllers, "Reporting", None)
|
||||
if Reporting is not None:
|
||||
Reporting.report_start_event(
|
||||
context.full_name(), description, context.level)
|
||||
if self._push_to_progress(context):
|
||||
msg = context.full_name()
|
||||
if description:
|
||||
msg += ': ' + description
|
||||
self.controllers.InstallProgress.progress_view.event_start(
|
||||
context, msg)
|
||||
for listener in self.event_listeners:
|
||||
listener.report_start_event(context, description)
|
||||
|
||||
def report_finish_event(self, context, description, status):
|
||||
Reporting = getattr(self.controllers, "Reporting", None)
|
||||
if Reporting is not None:
|
||||
Reporting.report_finish_event(
|
||||
context.full_name(), description, status, context.level)
|
||||
if self._push_to_progress(context):
|
||||
self.controllers.InstallProgress.progress_view.event_finish(
|
||||
context)
|
||||
for listener in self.event_listeners:
|
||||
listener.report_finish_event(context, description, status)
|
||||
|
||||
def confirm_install(self):
|
||||
self.install_confirmed = True
|
||||
|
@ -417,8 +393,7 @@ class Subiquity(Application):
|
|||
self.ui.set_body(self.controllers.InstallProgress.progress_view)
|
||||
|
||||
async def _apply(self, controller):
|
||||
with controller.context.child("apply_autoinstall_config"):
|
||||
await controller.apply_autoinstall_config()
|
||||
await controller.apply_autoinstall_config()
|
||||
controller.autoinstall_applied = True
|
||||
controller.configured()
|
||||
self.next_screen()
|
||||
|
|
|
@ -96,11 +96,13 @@ class ProgressView(BaseView):
|
|||
def event_start(self, context, message):
|
||||
self.event_finish(context.parent)
|
||||
walker = self.event_listbox.base_widget.body
|
||||
indent = ' ' * (context.full_name().count('/') - 2)
|
||||
indent = context.full_name().count('/') - 2
|
||||
if context.get('is-install-context'):
|
||||
indent -= 1
|
||||
spinner = Spinner(self.controller.app.aio_loop)
|
||||
spinner.start()
|
||||
new_line = Columns([
|
||||
('pack', Text(indent + message)),
|
||||
('pack', Text(' ' * indent + message)),
|
||||
('pack', spinner),
|
||||
], dividechars=1)
|
||||
self.ongoing[context] = len(walker)
|
||||
|
|
|
@ -109,3 +109,20 @@ class Context:
|
|||
return c.data[key]
|
||||
c = c.parent
|
||||
return default
|
||||
|
||||
|
||||
def with_context(name=None, description="", **context_kw):
|
||||
def decorate(meth):
|
||||
nonlocal name
|
||||
if name is None:
|
||||
name = meth.__name__
|
||||
|
||||
async def decorated(self, context=None, *args, **kw):
|
||||
if context is None:
|
||||
context = self.context
|
||||
manager = context.child(
|
||||
name, description=description.format(**kw), **context_kw)
|
||||
with manager as subcontext:
|
||||
await meth(self, subcontext, *args, **kw)
|
||||
return decorated
|
||||
return decorate
|
||||
|
|
|
@ -23,6 +23,7 @@ import yaml
|
|||
from probert.network import IFF_UP, NetworkEventReceiver
|
||||
|
||||
from subiquitycore.async_helpers import SingleInstanceTask
|
||||
from subiquitycore.context import with_context
|
||||
from subiquitycore.controller import BaseController
|
||||
from subiquitycore.file_util import write_file
|
||||
from subiquitycore.models.network import (
|
||||
|
@ -315,8 +316,8 @@ class NetworkController(BaseController):
|
|||
netplan_config_file_name = '00-snapd-config.yaml'
|
||||
return os.path.join(self.root, 'etc/netplan', netplan_config_file_name)
|
||||
|
||||
def apply_config(self, silent=False):
|
||||
self.apply_config_task.start_sync(silent)
|
||||
def apply_config(self, context=None, silent=False):
|
||||
self.apply_config_task.start_sync(context, silent=silent)
|
||||
|
||||
async def _down_devs(self, devs):
|
||||
for dev in devs:
|
||||
|
@ -358,115 +359,115 @@ class NetworkController(BaseController):
|
|||
|
||||
self.parse_netplan_configs()
|
||||
|
||||
async def _apply_config(self, silent):
|
||||
with self.context.child(
|
||||
"apply_config", "silent={}".format(silent), level="INFO"):
|
||||
devs_to_delete = []
|
||||
devs_to_down = []
|
||||
dhcp_device_versions = []
|
||||
dhcp_events = set()
|
||||
for dev in self.model.get_all_netdevs(include_deleted=True):
|
||||
dev.dhcp_events = {}
|
||||
for v in 4, 6:
|
||||
if dev.dhcp_enabled(v):
|
||||
if not silent:
|
||||
dev.set_dhcp_state(v, "PENDING")
|
||||
self.network_event_receiver.update_link(
|
||||
dev.ifindex)
|
||||
else:
|
||||
dev.set_dhcp_state(v, "RECONFIGURE")
|
||||
dev.dhcp_events[v] = e = asyncio.Event()
|
||||
dhcp_events.add(e)
|
||||
if dev.info is None:
|
||||
continue
|
||||
if dev.config != self.model.config.config_for_device(dev.info):
|
||||
if dev.is_virtual:
|
||||
devs_to_delete.append(dev)
|
||||
@with_context(
|
||||
name="apply_config", description="silent={silent}", level="INFO")
|
||||
async def _apply_config(self, context, *, silent):
|
||||
devs_to_delete = []
|
||||
devs_to_down = []
|
||||
dhcp_device_versions = []
|
||||
dhcp_events = set()
|
||||
for dev in self.model.get_all_netdevs(include_deleted=True):
|
||||
dev.dhcp_events = {}
|
||||
for v in 4, 6:
|
||||
if dev.dhcp_enabled(v):
|
||||
if not silent:
|
||||
dev.set_dhcp_state(v, "PENDING")
|
||||
self.network_event_receiver.update_link(
|
||||
dev.ifindex)
|
||||
else:
|
||||
devs_to_down.append(dev)
|
||||
|
||||
self._write_config()
|
||||
|
||||
if not silent and self.view:
|
||||
self.view.show_apply_spinner()
|
||||
|
||||
try:
|
||||
def error(stage):
|
||||
if not silent and self.view:
|
||||
self.view.show_network_error(stage)
|
||||
|
||||
if self.opts.dry_run:
|
||||
delay = 1/self.app.scale_factor
|
||||
await arun_command(['sleep', str(delay)])
|
||||
if os.path.exists('/lib/netplan/generate'):
|
||||
# If netplan appears to be installed, run generate to
|
||||
# at least test that what we wrote is acceptable to
|
||||
# netplan.
|
||||
await arun_command(
|
||||
['netplan', 'generate', '--root', self.root],
|
||||
check=True)
|
||||
dev.set_dhcp_state(v, "RECONFIGURE")
|
||||
dev.dhcp_events[v] = e = asyncio.Event()
|
||||
dhcp_events.add(e)
|
||||
if dev.info is None:
|
||||
continue
|
||||
if dev.config != self.model.config.config_for_device(dev.info):
|
||||
if dev.is_virtual:
|
||||
devs_to_delete.append(dev)
|
||||
else:
|
||||
if devs_to_down or devs_to_delete:
|
||||
try:
|
||||
await arun_command(
|
||||
['systemctl', 'mask', '--runtime',
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket'],
|
||||
check=True)
|
||||
await arun_command(
|
||||
['systemctl', 'stop',
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket'],
|
||||
check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
error("stop-networkd")
|
||||
raise
|
||||
if devs_to_down:
|
||||
await self._down_devs(devs_to_down)
|
||||
if devs_to_delete:
|
||||
await self._delete_devs(devs_to_delete)
|
||||
if devs_to_down or devs_to_delete:
|
||||
devs_to_down.append(dev)
|
||||
|
||||
self._write_config()
|
||||
|
||||
if not silent and self.view:
|
||||
self.view.show_apply_spinner()
|
||||
|
||||
try:
|
||||
def error(stage):
|
||||
if not silent and self.view:
|
||||
self.view.show_network_error(stage)
|
||||
|
||||
if self.opts.dry_run:
|
||||
delay = 1/self.app.scale_factor
|
||||
await arun_command(['sleep', str(delay)])
|
||||
if os.path.exists('/lib/netplan/generate'):
|
||||
# If netplan appears to be installed, run generate to
|
||||
# at least test that what we wrote is acceptable to
|
||||
# netplan.
|
||||
await arun_command(
|
||||
['netplan', 'generate', '--root', self.root],
|
||||
check=True)
|
||||
else:
|
||||
if devs_to_down or devs_to_delete:
|
||||
try:
|
||||
await arun_command(
|
||||
['systemctl', 'unmask', '--runtime',
|
||||
['systemctl', 'mask', '--runtime',
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket'],
|
||||
check=True)
|
||||
try:
|
||||
await arun_command(['netplan', 'apply'], check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
error("apply")
|
||||
raise
|
||||
if devs_to_down or devs_to_delete:
|
||||
# It's probably running already, but just in case.
|
||||
await arun_command(
|
||||
['systemctl', 'start', 'systemd-networkd.socket'],
|
||||
check=False)
|
||||
finally:
|
||||
if not silent and self.view:
|
||||
self.view.hide_apply_spinner()
|
||||
['systemctl', 'stop',
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket'],
|
||||
check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
error("stop-networkd")
|
||||
raise
|
||||
if devs_to_down:
|
||||
await self._down_devs(devs_to_down)
|
||||
if devs_to_delete:
|
||||
await self._delete_devs(devs_to_delete)
|
||||
if devs_to_down or devs_to_delete:
|
||||
await arun_command(
|
||||
['systemctl', 'unmask', '--runtime',
|
||||
'systemd-networkd.service',
|
||||
'systemd-networkd.socket'],
|
||||
check=True)
|
||||
try:
|
||||
await arun_command(['netplan', 'apply'], check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
error("apply")
|
||||
raise
|
||||
if devs_to_down or devs_to_delete:
|
||||
# It's probably running already, but just in case.
|
||||
await arun_command(
|
||||
['systemctl', 'start', 'systemd-networkd.socket'],
|
||||
check=False)
|
||||
finally:
|
||||
if not silent and self.view:
|
||||
self.view.hide_apply_spinner()
|
||||
|
||||
if self.answers.get('accept-default', False):
|
||||
self.done()
|
||||
elif self.answers.get('actions', False):
|
||||
actions = self.answers['actions']
|
||||
self.answers.clear()
|
||||
self._run_iterator(self._run_actions(actions))
|
||||
if self.answers.get('accept-default', False):
|
||||
self.done()
|
||||
elif self.answers.get('actions', False):
|
||||
actions = self.answers['actions']
|
||||
self.answers.clear()
|
||||
self._run_iterator(self._run_actions(actions))
|
||||
|
||||
if not dhcp_events:
|
||||
return
|
||||
if not dhcp_events:
|
||||
return
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.wait({e.wait() for e in dhcp_events}),
|
||||
10)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
asyncio.wait({e.wait() for e in dhcp_events}),
|
||||
10)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
for dev, v in dhcp_device_versions:
|
||||
dev.dhcp_events = {}
|
||||
if not dev.dhcp_addresses()[v]:
|
||||
dev.set_dhcp_state(v, "TIMEDOUT")
|
||||
self.network_event_receiver.update_link(dev.ifindex)
|
||||
for dev, v in dhcp_device_versions:
|
||||
dev.dhcp_events = {}
|
||||
if not dev.dhcp_addresses()[v]:
|
||||
dev.set_dhcp_state(v, "TIMEDOUT")
|
||||
self.network_event_receiver.update_link(dev.ifindex)
|
||||
|
||||
def add_vlan(self, device, vlan):
|
||||
return self.model.new_vlan(device, vlan)
|
||||
|
|
Loading…
Reference in New Issue