make a helpful async wrapper around snapd
This commit is contained in:
parent
3ba3b04e09
commit
67be814dc3
|
@ -13,9 +13,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import enum
|
import enum
|
||||||
from functools import partial
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -25,7 +23,6 @@ from subiquitycore.controller import BaseController
|
||||||
from subiquitycore.core import Skip
|
from subiquitycore.core import Skip
|
||||||
|
|
||||||
from subiquity.async_helpers import (
|
from subiquity.async_helpers import (
|
||||||
run_in_thread,
|
|
||||||
schedule_task,
|
schedule_task,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -74,14 +71,11 @@ class RefreshController(BaseController):
|
||||||
|
|
||||||
async def configure_snapd(self):
|
async def configure_snapd(self):
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
r = await self.app.snapd.get(
|
||||||
self.app.snapd_connection.get,
|
|
||||||
'v2/snaps/{snap_name}'.format(snap_name=self.snap_name))
|
'v2/snaps/{snap_name}'.format(snap_name=self.snap_name))
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
log.exception("getting snap details")
|
log.exception("getting snap details")
|
||||||
return
|
return
|
||||||
r = response.json()
|
|
||||||
self.current_snap_version = r['result']['version']
|
self.current_snap_version = r['result']['version']
|
||||||
for k in 'channel', 'revision', 'version':
|
for k in 'channel', 'revision', 'version':
|
||||||
self.app.note_data_for_apport(
|
self.app.note_data_for_apport(
|
||||||
|
@ -92,27 +86,12 @@ class RefreshController(BaseController):
|
||||||
channel = self.get_refresh_channel()
|
channel = self.get_refresh_channel()
|
||||||
log.debug("switching %s to %s", self.snap_name, channel)
|
log.debug("switching %s to %s", self.snap_name, channel)
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
await self.app.snapd.post_and_wait(
|
||||||
self.app.snapd_connection.post,
|
|
||||||
'v2/snaps/{}'.format(self.snap_name),
|
'v2/snaps/{}'.format(self.snap_name),
|
||||||
{'action': 'switch', 'channel': channel})
|
{'action': 'switch', 'channel': channel})
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
log.exception("switching channels")
|
log.exception("switching channels")
|
||||||
return
|
return
|
||||||
change = response.json()["change"]
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
response = await run_in_thread(
|
|
||||||
self.app.snapd_connection.get,
|
|
||||||
'v2/changes/{}'.format(change))
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException:
|
|
||||||
log.exception("checking switch")
|
|
||||||
return
|
|
||||||
if response.json()["result"]["status"] == "Done":
|
|
||||||
break
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
log.debug("snap switching completed")
|
log.debug("snap switching completed")
|
||||||
self.switch_state = SwitchState.SWITCHED
|
self.switch_state = SwitchState.SWITCHED
|
||||||
self._maybe_check_for_update()
|
self._maybe_check_for_update()
|
||||||
|
@ -170,12 +149,7 @@ class RefreshController(BaseController):
|
||||||
|
|
||||||
async def check_for_update(self):
|
async def check_for_update(self):
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
result = await self.app.snapd.get('v2/find', select='refresh')
|
||||||
partial(
|
|
||||||
self.app.snapd_connection.get,
|
|
||||||
'v2/find',
|
|
||||||
select='refresh'))
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log.exception("checking for update")
|
log.exception("checking for update")
|
||||||
self.check_error = e
|
self.check_error = e
|
||||||
|
@ -186,7 +160,6 @@ class RefreshController(BaseController):
|
||||||
# ones!
|
# ones!
|
||||||
if self.check_state.is_definite():
|
if self.check_state.is_definite():
|
||||||
return
|
return
|
||||||
result = response.json()
|
|
||||||
log.debug("_check_result %s", result)
|
log.debug("_check_result %s", result)
|
||||||
for snap in result["result"]:
|
for snap in result["result"]:
|
||||||
if snap["name"] == self.snap_name:
|
if snap["name"] == self.snap_name:
|
||||||
|
@ -208,35 +181,29 @@ class RefreshController(BaseController):
|
||||||
|
|
||||||
async def _start_update(self, callback):
|
async def _start_update(self, callback):
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
change = await self.app.snapd.post(
|
||||||
self.app.snapd_connection.post,
|
|
||||||
'v2/snaps/{}'.format(self.snap_name),
|
'v2/snaps/{}'.format(self.snap_name),
|
||||||
{'action': 'refresh'})
|
{'action': 'refresh'})
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log.exception("requesting update")
|
log.exception("requesting update")
|
||||||
self.update_state = CheckState.FAILED
|
self.update_state = CheckState.FAILED
|
||||||
self.update_failure = e
|
self.update_failure = e
|
||||||
return
|
return
|
||||||
result = response.json()
|
log.debug("refresh requested: %s", change)
|
||||||
log.debug("refresh requested: %s", result)
|
callback(change)
|
||||||
callback(result['change'])
|
|
||||||
|
|
||||||
def get_progress(self, change, callback):
|
def get_progress(self, change, callback):
|
||||||
schedule_task(self._get_progress(change, callback))
|
schedule_task(self._get_progress(change, callback))
|
||||||
|
|
||||||
async def _get_progress(self, change, callback):
|
async def _get_progress(self, change, callback):
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
result = await self.app.snapd.get(
|
||||||
self.app.snapd_connection.get,
|
|
||||||
'v2/changes/{}'.format(change))
|
'v2/changes/{}'.format(change))
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log.exception("checking for progress")
|
log.exception("checking for progress")
|
||||||
self.update_state = CheckState.FAILED
|
self.update_state = CheckState.FAILED
|
||||||
self.update_failure = e
|
self.update_failure = e
|
||||||
return
|
return
|
||||||
result = response.json()
|
|
||||||
callback(result['result'])
|
callback(result['result'])
|
||||||
|
|
||||||
def start_ui(self, index=1):
|
def start_ui(self, index=1):
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
@ -22,7 +21,6 @@ from subiquitycore.controller import BaseController
|
||||||
from subiquitycore.core import Skip
|
from subiquitycore.core import Skip
|
||||||
|
|
||||||
from subiquity.async_helpers import (
|
from subiquity.async_helpers import (
|
||||||
run_in_thread,
|
|
||||||
schedule_task,
|
schedule_task,
|
||||||
)
|
)
|
||||||
from subiquity.models.snaplist import SnapSelection
|
from subiquity.models.snaplist import SnapSelection
|
||||||
|
@ -33,16 +31,15 @@ log = logging.getLogger('subiquity.controllers.snaplist')
|
||||||
|
|
||||||
class SnapdSnapInfoLoader:
|
class SnapdSnapInfoLoader:
|
||||||
|
|
||||||
def __init__(self, model, app, connection, store_section):
|
def __init__(self, model, snapd, store_section):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.app = app
|
|
||||||
self.store_section = store_section
|
self.store_section = store_section
|
||||||
|
|
||||||
self._running = False
|
self._running = False
|
||||||
self.snap_list_fetched = False
|
self.snap_list_fetched = False
|
||||||
self.failed = False
|
self.failed = False
|
||||||
|
|
||||||
self.connection = connection
|
self.snapd = snapd
|
||||||
self.pending_info_snaps = []
|
self.pending_info_snaps = []
|
||||||
self.ongoing = {} # {snap:[callbacks]}
|
self.ongoing = {} # {snap:[callbacks]}
|
||||||
|
|
||||||
|
@ -54,12 +51,8 @@ class SnapdSnapInfoLoader:
|
||||||
async def _start(self):
|
async def _start(self):
|
||||||
self.ongoing[None] = []
|
self.ongoing[None] = []
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
result = await self.snapd.get(
|
||||||
partial(
|
'v2/find', section=self.store_section)
|
||||||
self.connection.get,
|
|
||||||
'v2/find',
|
|
||||||
section=self.store_section))
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
log.exception("loading list of snaps failed")
|
log.exception("loading list of snaps failed")
|
||||||
self.failed = True
|
self.failed = True
|
||||||
|
@ -67,7 +60,7 @@ class SnapdSnapInfoLoader:
|
||||||
return
|
return
|
||||||
if not self._running:
|
if not self._running:
|
||||||
return
|
return
|
||||||
self.model.load_find_data(response.json())
|
self.model.load_find_data(result)
|
||||||
self.snap_list_fetched = True
|
self.snap_list_fetched = True
|
||||||
self.pending_snaps = self.model.get_snap_list()
|
self.pending_snaps = self.model.get_snap_list()
|
||||||
log.debug("fetched list of %s snaps", len(self.model.get_snap_list()))
|
log.debug("fetched list of %s snaps", len(self.model.get_snap_list()))
|
||||||
|
@ -84,19 +77,14 @@ class SnapdSnapInfoLoader:
|
||||||
async def _fetch_info_for_snap(self, snap):
|
async def _fetch_info_for_snap(self, snap):
|
||||||
log.debug('starting fetch for %s', snap.name)
|
log.debug('starting fetch for %s', snap.name)
|
||||||
try:
|
try:
|
||||||
response = await run_in_thread(
|
data = await self.snapd.get(
|
||||||
partial(
|
'v2/find', name=snap.name)
|
||||||
self.connection.get,
|
|
||||||
'v2/find',
|
|
||||||
name=snap.name))
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
log.exception("loading snap info failed")
|
log.exception("loading snap info failed")
|
||||||
# XXX something better here?
|
# XXX something better here?
|
||||||
return
|
return
|
||||||
if not self._running:
|
if not self._running:
|
||||||
return
|
return
|
||||||
data = response.json()
|
|
||||||
log.debug('got data for %s', snap.name)
|
log.debug('got data for %s', snap.name)
|
||||||
self.model.load_info_data(data)
|
self.model.load_info_data(data)
|
||||||
for cb in self.ongoing.pop(snap):
|
for cb in self.ongoing.pop(snap):
|
||||||
|
@ -131,7 +119,7 @@ class SnapListController(BaseController):
|
||||||
|
|
||||||
def _make_loader(self):
|
def _make_loader(self):
|
||||||
return SnapdSnapInfoLoader(
|
return SnapdSnapInfoLoader(
|
||||||
self.model, self.app, self.app.snapd_connection,
|
self.model, self.app.snapd,
|
||||||
self.opts.snap_section)
|
self.opts.snap_section)
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
|
|
|
@ -29,6 +29,7 @@ from subiquity.controllers.error import (
|
||||||
)
|
)
|
||||||
from subiquity.models.subiquity import SubiquityModel
|
from subiquity.models.subiquity import SubiquityModel
|
||||||
from subiquity.snapd import (
|
from subiquity.snapd import (
|
||||||
|
AsyncSnapd,
|
||||||
FakeSnapdConnection,
|
FakeSnapdConnection,
|
||||||
SnapdConnection,
|
SnapdConnection,
|
||||||
)
|
)
|
||||||
|
@ -98,7 +99,7 @@ class Subiquity(Application):
|
||||||
"examples", "snaps"))
|
"examples", "snaps"))
|
||||||
else:
|
else:
|
||||||
connection = SnapdConnection(self.root, self.snapd_socket_path)
|
connection = SnapdConnection(self.root, self.snapd_socket_path)
|
||||||
self.snapd_connection = connection
|
self.snapd = AsyncSnapd(connection)
|
||||||
self.signal.connect_signals([
|
self.signal.connect_signals([
|
||||||
('network-proxy-set', self._proxy_set),
|
('network-proxy-set', self._proxy_set),
|
||||||
('network-change', self._network_change),
|
('network-change', self._network_change),
|
||||||
|
@ -129,7 +130,7 @@ class Subiquity(Application):
|
||||||
|
|
||||||
def _proxy_set(self):
|
def _proxy_set(self):
|
||||||
self.run_in_bg(
|
self.run_in_bg(
|
||||||
lambda: self.snapd_connection.configure_proxy(
|
lambda: self.snapd.connection.configure_proxy(
|
||||||
self.base_model.proxy),
|
self.base_model.proxy),
|
||||||
lambda fut: (
|
lambda fut: (
|
||||||
fut.result(), self.signal.emit_signal('snapd-network-change')),
|
fut.result(), self.signal.emit_signal('snapd-network-change')),
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from functools import partial
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -23,6 +25,7 @@ from urllib.parse import (
|
||||||
urlencode,
|
urlencode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from subiquity.async_helpers import run_in_thread
|
||||||
from subiquitycore.utils import run_command
|
from subiquitycore.utils import run_command
|
||||||
|
|
||||||
import requests_unixsocket
|
import requests_unixsocket
|
||||||
|
@ -157,3 +160,30 @@ class FakeSnapdConnection:
|
||||||
return rs.next()
|
return rs.next()
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Don't know how to fake GET response to {}".format((path, args)))
|
"Don't know how to fake GET response to {}".format((path, args)))
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncSnapd:
|
||||||
|
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
async def get(self, path, **args):
|
||||||
|
response = await run_in_thread(
|
||||||
|
partial(self.connection.get, path, **args))
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
async def post(self, path, body, **args):
|
||||||
|
response = await run_in_thread(
|
||||||
|
partial(self.connection.post, path, body, **args))
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()['change']
|
||||||
|
|
||||||
|
async def post_and_wait(self, path, body, **args):
|
||||||
|
change = await self.post(path, body, **args)
|
||||||
|
change_path = 'v2/changes/{}'.format(change)
|
||||||
|
while True:
|
||||||
|
result = await self.get(change_path)
|
||||||
|
if result["result"]["status"] == "Done":
|
||||||
|
break
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
Loading…
Reference in New Issue