Skip to content

Commit

Permalink
Add getting and setting snap config (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
st3v3nmw committed Jan 22, 2024
1 parent e812505 commit 7f1be98
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 426 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ jobs:
matrix:
os: ["ubuntu-22.04", "ubuntu-20.04"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
- run: |
make depends
# -common seems a catch-22, but this is just a shortcut to
Expand All @@ -17,6 +19,8 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
- run: make depends
- run: make lint
2 changes: 2 additions & 0 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- run: |
make depends
# -common seems a catch-22, but this is just a shortcut to
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "snap-http"]
path = snap-http
url = https://github.com/Perfect5th/snap-http
40 changes: 21 additions & 19 deletions landscape/client/manager/snapmanager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import json
import logging
from collections import deque

from twisted.internet import task

from landscape.client import snap_http
from landscape.client.manager.plugin import FAILED
from landscape.client.manager.plugin import ManagerPlugin
from landscape.client.manager.plugin import SUCCEEDED
from landscape.client.snap.http import INCOMPLETE_STATUSES
from landscape.client.snap.http import SnapdHttpException
from landscape.client.snap.http import SnapHttp
from landscape.client.snap.http import SUCCESS_STATUSES
from landscape.client.snap_http import INCOMPLETE_STATUSES
from landscape.client.snap_http import SnapdHttpException
from landscape.client.snap_http import SUCCESS_STATUSES


class SnapManager(ManagerPlugin):
Expand All @@ -23,18 +24,18 @@ class SnapManager(ManagerPlugin):
def __init__(self):
super().__init__()

self._snap_http = SnapHttp()
self.SNAP_METHODS = {
"install-snaps": self._snap_http.install_snap,
"install-snaps-batch": self._snap_http.install_snaps,
"remove-snaps": self._snap_http.remove_snap,
"remove-snaps-batch": self._snap_http.remove_snaps,
"refresh-snaps": self._snap_http.refresh_snap,
"refresh-snaps-batch": self._snap_http.refresh_snaps,
"hold-snaps": self._snap_http.hold_snap,
"hold-snaps-batch": self._snap_http.hold_snaps,
"unhold-snaps": self._snap_http.unhold_snap,
"unhold-snaps-batch": self._snap_http.unhold_snaps,
"install-snaps": snap_http.install,
"install-snaps-batch": snap_http.install_all,
"remove-snaps": snap_http.remove,
"remove-snaps-batch": snap_http.remove_all,
"refresh-snaps": snap_http.refresh,
"refresh-snaps-batch": snap_http.refresh_all,
"hold-snaps": snap_http.hold,
"hold-snaps-batch": snap_http.hold_all,
"unhold-snaps": snap_http.unhold,
"unhold-snaps-batch": snap_http.unhold_all,
"set-snap-config": snap_http.set_conf,
}

def register(self, registry):
Expand All @@ -46,6 +47,7 @@ def register(self, registry):
registry.register_message("refresh-snaps", self._handle_snap_task)
registry.register_message("hold-snaps", self._handle_snap_task)
registry.register_message("unhold-snaps", self._handle_snap_task)
registry.register_message("set-snap-config", self._handle_snap_task)

def _handle_snap_task(self, message):
"""
Expand Down Expand Up @@ -86,7 +88,7 @@ def _handle_batch_snap_task(self, message):
)
queue.append((response["change"], "BATCH"))
except SnapdHttpException as e:
result = e.json["result"]
result = json.loads(e.args[0])["result"]
logging.error(
f"Error in {message_type}: {message}",
)
Expand Down Expand Up @@ -130,7 +132,7 @@ def _handle_multiple_snap_tasks(self, message):
)
queue.append((response["change"], name))
except SnapdHttpException as e:
result = e.json["result"]
result = json.loads(e.args[0])["result"]
logging.error(
f"Error in {message_type} for '{name}': {message}",
)
Expand Down Expand Up @@ -161,7 +163,7 @@ def get_status():
logging.info("Polling snapd for status of pending snap changes")

try:
result = self._snap_http.check_changes().get("result", [])
result = snap_http.check_changes().get("result", [])
result_dict = {c["id"]: c for c in result}
except SnapdHttpException as e:
logging.error(f"Error checking status of snap changes: {e}")
Expand Down Expand Up @@ -253,7 +255,7 @@ def _respond(self, snap_results, opid, errors):

def _send_installed_snap_update(self):
try:
installed_snaps = self._snap_http.get_snaps()
installed_snaps = snap_http.list().result
except SnapdHttpException as e:
logging.error(
f"Unable to list installed snaps after snap change: {e}",
Expand Down
160 changes: 129 additions & 31 deletions landscape/client/manager/tests/test_snapmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from landscape.client.manager.manager import FAILED
from landscape.client.manager.manager import SUCCEEDED
from landscape.client.manager.snapmanager import SnapManager
from landscape.client.snap.http import SnapdHttpException
from landscape.client.snap.http import SnapHttp as OrigSnapHttp
from landscape.client.snap_http import SnapdHttpException
from landscape.client.snap_http import SnapdResponse
from landscape.client.tests.helpers import LandscapeTest
from landscape.client.tests.helpers import ManagerHelper

Expand All @@ -15,13 +15,10 @@ class SnapManagerTest(LandscapeTest):
def setUp(self):
super().setUp()

self.snap_http = mock.Mock(spec_set=OrigSnapHttp)
self.SnapHttp = mock.patch(
"landscape.client.manager.snapmanager.SnapHttp",
self.snap_http = mock.patch(
"landscape.client.manager.snapmanager.snap_http",
).start()

self.SnapHttp.return_value = self.snap_http

self.broker_service.message_store.set_accepted_types(
["operation-result"],
)
Expand Down Expand Up @@ -49,14 +46,19 @@ def install_snap(name, revision=None, channel=None, classic=False):

return mock.DEFAULT

self.snap_http.install_snap.side_effect = install_snap
self.snap_http.install.side_effect = install_snap
self.snap_http.check_changes.return_value = {
"result": [
{"id": "1", "status": "Done"},
{"id": "2", "status": "Done"},
],
}
self.snap_http.get_snaps.return_value = {"installed": []}
self.snap_http.list.return_value = SnapdResponse(
"sync",
200,
"OK",
{"installed": []},
)

result = self.manager.dispatch_message(
{
Expand Down Expand Up @@ -90,23 +92,28 @@ def test_install_snaps_batch(self):
When no channels or revisions are specified, snaps are installed
via a single call to snapd.
"""
self.snap_http.install_snaps.return_value = {"change": "1"}
self.snap_http.install_all.return_value = {"change": "1"}
self.snap_http.check_changes.return_value = {
"result": [{"id": "1", "status": "Done"}],
}
self.snap_http.get_snaps.return_value = {
"installed": [
{
"name": "hello",
"id": "test",
"confinement": "strict",
"tracking-channel": "latest/stable",
"revision": "100",
"publisher": {"validation": "yep", "username": "me"},
"version": "1.2.3",
},
],
}
self.snap_http.list.return_value = SnapdResponse(
"sync",
200,
"OK",
{
"installed": [
{
"name": "hello",
"id": "test",
"confinement": "strict",
"tracking-channel": "latest/stable",
"revision": "100",
"publisher": {"validation": "yep", "username": "me"},
"version": "1.2.3",
},
],
},
)

result = self.manager.dispatch_message(
{
Expand Down Expand Up @@ -136,10 +143,15 @@ def got_result(r):
return result.addCallback(got_result)

def test_install_snap_immediate_error(self):
self.snap_http.install_snaps.side_effect = SnapdHttpException(
self.snap_http.install_all.side_effect = SnapdHttpException(
b'{"result": "whoops"}',
)
self.snap_http.get_snaps.return_value = {"installed": []}
self.snap_http.list.return_value = SnapdResponse(
"sync",
200,
"OK",
{"installed": []},
)

result = self.manager.dispatch_message(
{
Expand Down Expand Up @@ -168,9 +180,11 @@ def got_result(r):
return result.addCallback(got_result)

def test_install_snap_no_status(self):
self.snap_http.install_snaps.return_value = {"change": "1"}
self.snap_http.install_all.return_value = {"change": "1"}
self.snap_http.check_changes.return_value = {"result": []}
self.snap_http.get_snaps.return_value = {"installed": []}
self.snap_http.list.return_value = SnapdResponse(
"sync", 200, "OK", {"installed": []}
)

result = self.manager.dispatch_message(
{
Expand All @@ -197,9 +211,11 @@ def got_result(r):
return result.addCallback(got_result)

def test_install_snap_check_error(self):
self.snap_http.install_snaps.return_value = {"change": "1"}
self.snap_http.install_all.return_value = {"change": "1"}
self.snap_http.check_changes.side_effect = SnapdHttpException("whoops")
self.snap_http.get_snaps.return_value = {"installed": []}
self.snap_http.list.return_value = SnapdResponse(
"sync", 200, "OK", {"installed": []}
)

result = self.manager.dispatch_message(
{
Expand Down Expand Up @@ -228,11 +244,13 @@ def got_result(r):
return result.addCallback(got_result)

def test_remove_snap(self):
self.snap_http.remove_snaps.return_value = {"change": "1"}
self.snap_http.remove_all.return_value = {"change": "1"}
self.snap_http.check_changes.return_value = {
"result": [{"id": "1", "status": "Done"}],
}
self.snap_http.get_snaps.return_value = {"installed": []}
self.snap_http.list.return_value = SnapdResponse(
"sync", 200, "OK", {"installed": []}
)

result = self.manager.dispatch_message(
{
Expand All @@ -257,3 +275,83 @@ def got_result(r):
)

return result.addCallback(got_result)

def test_set_config(self):
self.snap_http.set_conf.return_value = {"change": "1"}
self.snap_http.check_changes.return_value = {
"result": [{"id": "1", "status": "Done"}],
}
self.snap_http.list.return_value = SnapdResponse(
"sync", 200, "OK", {"installed": []}
)

result = self.manager.dispatch_message(
{
"type": "set-snap-config",
"operation-id": 123,
"snaps": [
{
"name": "hello",
"config": {"foo": {"bar": "qux", "baz": "quux"}},
}
],
}
)

def got_result(r):
self.assertMessages(
self.broker_service.message_store.get_pending_messages(),
[
{
"type": "operation-result",
"status": SUCCEEDED,
"result-text": "{'completed': ['hello'], "
"'errored': [], 'errors': {}}",
"operation-id": 123,
},
],
)

return result.addCallback(got_result)

def test_set_config_sync_error(self):
self.snap_http.set_conf.side_effect = SnapdHttpException(
b'{"result": "whoops"}',
)
self.snap_http.check_changes.return_value = {
"result": [{"id": "1", "status": "Done"}],
}
self.snap_http.list.return_value = SnapdResponse(
"sync", 200, "OK", {"installed": []}
)

result = self.manager.dispatch_message(
{
"type": "set-snap-config",
"operation-id": 123,
"snaps": [
{
"name": "hello",
"config": {"foo": {"bar": "qux", "baz": "quux"}},
}
],
}
)

def got_result(r):
self.assertMessages(
self.broker_service.message_store.get_pending_messages(),
[
{
"type": "operation-result",
"status": FAILED,
"result-text": (
"{'completed': [], 'errored': [], "
"'errors': {'hello': 'whoops'}}"
),
"operation-id": 123,
},
],
)

return result.addCallback(got_result)
Loading

0 comments on commit 7f1be98

Please sign in to comment.