From f1177788e14f617fb09670abb3ab2c9ae055c807 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 29 Mar 2019 14:45:11 +1300 Subject: [PATCH] snap switch subiquity to stable/ubuntu-XX.YY.Z also support passing subquity-channel= on the kernel command line --- examples/snaps/v2-changes-8/0000.json | 29 +++++++++ subiquity/controllers/refresh.py | 87 ++++++++++++++++++++++++++- subiquity/snapd.py | 7 +++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 examples/snaps/v2-changes-8/0000.json diff --git a/examples/snaps/v2-changes-8/0000.json b/examples/snaps/v2-changes-8/0000.json new file mode 100644 index 00000000..823ad7ca --- /dev/null +++ b/examples/snaps/v2-changes-8/0000.json @@ -0,0 +1,29 @@ +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "id": "176", + "kind": "switch-snap", + "summary": "Switch \"subiquity\" snap to stable", + "status": "Done", + "tasks": [ + { + "id": "4715", + "kind": "switch-snap", + "summary": "Switch snap \"subiquity\" to stable", + "status": "Done", + "progress": { + "label": "", + "done": 1, + "total": 1 + }, + "spawn-time": "2019-03-29T14:13:32.922119778+13:00", + "ready-time": "2019-03-29T14:13:32.941910157+13:00" + } + ], + "ready": true, + "spawn-time": "2019-03-29T14:13:32.922139818+13:00", + "ready-time": "2019-03-29T14:13:32.941911437+13:00" + } +} diff --git a/subiquity/controllers/refresh.py b/subiquity/controllers/refresh.py index 7596a7a1..db88f855 100644 --- a/subiquity/controllers/refresh.py +++ b/subiquity/controllers/refresh.py @@ -16,6 +16,7 @@ import enum import logging import os +import time import requests.exceptions @@ -36,6 +37,11 @@ class CheckState(enum.IntEnum): def is_definite(self): return self in [self.AVAILABLE, self.UNAVAILABLE] +class SwitchState(enum.IntEnum): + NOT_STARTED = enum.auto() + SWITCHING = enum.auto() + SWITCHED = enum.auto() + class RefreshController(BaseController): @@ -47,11 +53,90 @@ class RefreshController(BaseController): super().__init__(common) self.snap_name = os.environ.get("SNAP_NAME", "subiquity") self.check_state = CheckState.NOT_STARTED + self.switch_state = SwitchState.NOT_STARTED + self.network_state = "down" self.view = None self.offered_first_time = False self.answers = self.all_answers.get("Refresh", {}) + def start(self): + self.switch_state = SwitchState.SWITCHING + channel = self.get_refresh_channel() + self.run_in_bg( + lambda: self._bg_switch_snap(channel), + self._snap_switched) + + def get_refresh_channel(self): + """Return the channel we should refresh subiquity to.""" + + with open('/proc/cmdline') as fp: + cmdline = fp.read() + prefix="subquity-channel=" + for arg in cmdline.split(): + if arg.startswith(prefix): + log.debug("found cmdline arg %s", arg) + return arg[len(prefix):] + + info_file = '/cdrom/.disk/info' + try: + fp = open(info_file) + except FileNotFoundError: + if self.opts.dry_run: + info = ( + 'Ubuntu-Server 18.04.2 LTS "Bionic Beaver" - ' + 'Release amd64 (20190214.3)') + else: + log.debug("failed to find .disk/info file") + return + else: + with fp: + info = fp.read() + release = info.split()[1] + return 'stable/ubuntu-' + release + + def _bg_switch_snap(self, channel): + log.debug("switching %s to %s", self.snap_name, channel) + try: + response = self.snapd_connection.post( + 'v2/snaps/{}'.format(self.snap_name), + {'action': 'switch', 'channel': channel}) + response.raise_for_status() + except requests.exceptions.RequestException as e: + log.exception("switching") + return + change = response.json()["change"] + while True: + try: + response = self.snapd_connection.get( + 'v2/changes/{}'.format(change)) + response.raise_for_status() + except requests.exceptions.RequestException as e: + log.exception("checking switch") + return + if response.json()["result"]["status"] == "Done": + return + time.sleep(0.1) + + def _snap_switched(self, fut): + log.debug("snap switching completed") + try: + fut.result() + except: + log.exception("_snap_switched") + self.switch_state = SwitchState.SWITCHED + self._maybe_check_for_update() + def snapd_network_changed(self): + self.network_state = "up" + self._maybe_check_for_update() + + def _maybe_check_for_update(self): + # If we have not yet switched to the right channel, wait. + if self.switch_state != SwitchState.SWITCHED: + return + # If the network is not yet up, wait. + if self.network_state == "down": + return # If we restarted into this version, don't check for a new version. if self.updated: return @@ -98,7 +183,7 @@ class RefreshController(BaseController): def _bg_start_update(self): return self.snapd_connection.post( - 'v2/snaps/subiquity', {'action': 'refresh'}) + 'v2/snaps/{}'.format(self.snap_name), {'action': 'refresh'}) def update_started(self, fut, callback): try: diff --git a/subiquity/snapd.py b/subiquity/snapd.py index a2279f45..e3556de0 100644 --- a/subiquity/snapd.py +++ b/subiquity/snapd.py @@ -132,6 +132,13 @@ class FakeSnapdConnection: "status-code": 200, "status": "OK", }) + if path == "v2/snaps/subiquity" and body['action'] == 'switch': + return _FakeMemoryResponse({ + "type": "async", + "change": 8, + "status-code": 200, + "status": "Accepted", + }) raise Exception( "Don't know how to fake POST response to {}".format((path, args)))