Skip to content

Commit

Permalink
Apply suggestions from review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
weiiwang01 committed Nov 28, 2024
1 parent adfb4e5 commit b811c76
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 9 deletions.
9 changes: 6 additions & 3 deletions lib/charms/operator_libs_linux/v2/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def set(self, config: Dict[str, Any], *, typed: bool = False) -> None:
"""
if not typed:
config = {k: str(v) for k, v in config.items()}
self._snap_client.put_snap_config(self._name, config)
self._snap_client._put_snap_conf(self._name, config)

def unset(self, key) -> str:
"""Unset a snap configuration value.
Expand Down Expand Up @@ -774,12 +774,15 @@ def _request(
return self._wait(response["change"])
return response["result"]

def _wait(self, change_id: int) -> JSONType:
def _wait(self, change_id: str, timeout=300) -> JSONType:
"""Wait for an async change to complete.
The poll time is 100 milliseconds, the same as in snap clients.
"""
deadline = time.time() + timeout
while True:
if time.time() > deadline:
raise TimeoutError(f"timeout waiting for snap change {change_id}")
response = self._request("GET", f"changes/{change_id}")
status = response["status"]
if status == "Done":
Expand Down Expand Up @@ -840,7 +843,7 @@ def get_installed_snap_apps(self, name: str) -> List:
"""Query the snap server for apps belonging to a named, currently installed snap."""
return self._request("GET", "apps", {"names": name, "select": "service"})

def put_snap_config(self, name: str, conf: Dict[str, Any]):
def _put_snap_conf(self, name: str, conf: Dict[str, Any]):
"""Set the configuration details for an installed snap."""
return self._request("PUT", f"snaps/{name}/conf", body=conf)

Expand Down
150 changes: 144 additions & 6 deletions tests/unit/test_snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
# pyright: reportPrivateUsage=false

import datetime
import io
import json
import time
import typing
import unittest
from subprocess import CalledProcessError
from typing import Any, Dict, Iterable, Optional
Expand Down Expand Up @@ -697,6 +700,141 @@ def test_request_raw_bad_response_raises_snapapierror(self):
finally:
shutdown()

def test_wait_changes(self):
change_finished = False

def _request_raw(
method: str,
path: str,
query: Dict = None,
headers: Dict = None,
data: bytes = None,
) -> typing.IO[bytes]:
nonlocal change_finished
if method == "PUT" and path == "snaps/test/conf":
return io.BytesIO(
json.dumps(
{
"type": "async",
"status-code": 202,
"status": "Accepted",
"result": None,
"change": "97",
}
).encode("utf-8")
)
if method == "GET" and path == "changes/97" and not change_finished:
change_finished = True
return io.BytesIO(
json.dumps(
{
"type": "sync",
"status-code": 200,
"status": "OK",
"result": {
"id": "97",
"kind": "configure-snap",
"summary": 'Change configuration of "test" snap',
"status": "Doing",
"tasks": [
{
"id": "1029",
"kind": "run-hook",
"summary": 'Run configure hook of "test" snap',
"status": "Doing",
"progress": {"label": "", "done": 1, "total": 1},
"spawn-time": "2024-11-28T20:02:47.498399651+00:00",
"data": {"affected-snaps": ["test"]},
}
],
"ready": False,
"spawn-time": "2024-11-28T20:02:47.49842583+00:00",
},
}
).encode("utf-8")
)
if method == "GET" and path == "changes/97" and change_finished:
return io.BytesIO(
json.dumps(
{
"type": "sync",
"status-code": 200,
"status": "OK",
"result": {
"id": "98",
"kind": "configure-snap",
"summary": 'Change configuration of "test" snap',
"status": "Done",
"tasks": [
{
"id": "1030",
"kind": "run-hook",
"summary": 'Run configure hook of "test" snap',
"status": "Done",
"progress": {"label": "", "done": 1, "total": 1},
"spawn-time": "2024-11-28T20:06:41.415929854+00:00",
"ready-time": "2024-11-28T20:06:41.797437537+00:00",
"data": {"affected-snaps": ["test"]},
}
],
"ready": True,
"spawn-time": "2024-11-28T20:06:41.415955681+00:00",
"ready-time": "2024-11-28T20:06:41.797440022+00:00",
},
}
).encode("utf-8")
)
raise RuntimeError("unknown request")

client = snap.SnapClient()
with patch.object(client, "_request_raw", _request_raw), patch.object(time, "sleep"):
client._put_snap_conf("test", {"foo": "bar"})

def test_wait_failed(self):
def _request_raw(
method: str,
path: str,
query: Dict = None,
headers: Dict = None,
data: bytes = None,
) -> typing.IO[bytes]:
if method == "PUT" and path == "snaps/test/conf":
return io.BytesIO(
json.dumps(
{
"type": "async",
"status-code": 202,
"status": "Accepted",
"result": None,
"change": "97",
}
).encode("utf-8")
)
if method == "GET" and path == "changes/97":
return io.BytesIO(
json.dumps(
{
"type": "sync",
"status-code": 200,
"status": "OK",
"result": {
"id": "97",
"kind": "configure-snap",
"summary": 'Change configuration of "test" snap',
"status": "Error",
"ready": False,
"spawn-time": "2024-11-28T20:02:47.49842583+00:00",
},
}
).encode("utf-8")
)
raise RuntimeError("unknown request")

client = snap.SnapClient()
with patch.object(client, "_request_raw", _request_raw), patch.object(time, "sleep"):
with self.assertRaises(snap.SnapError):
client._put_snap_conf("test", {"foo": "bar"})


class TestSnapBareMethods(unittest.TestCase):
@patch("builtins.open", new_callable=mock_open, read_data="curl\n")
Expand Down Expand Up @@ -902,23 +1040,23 @@ def fake_snap(command: str, optargs: Optional[Iterable[str]] = None) -> str:
with self.assertRaises(TypeError):
foo.get(None) # pyright: ignore[reportArgumentType]

@patch("charms.operator_libs_linux.v2.snap.SnapClient.put_snap_config")
def test_snap_set_typed(self, put_snap_config):
@patch("charms.operator_libs_linux.v2.snap.SnapClient._put_snap_conf")
def test_snap_set_typed(self, put_snap_conf):
foo = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic")

config = {"n": 42, "s": "string", "d": {"nested": True}}

foo.set(config, typed=True)
put_snap_config.assert_called_with("foo", {"n": 42, "s": "string", "d": {"nested": True}})
put_snap_conf.assert_called_with("foo", {"n": 42, "s": "string", "d": {"nested": True}})

@patch("charms.operator_libs_linux.v2.snap.SnapClient.put_snap_config")
def test_snap_set_untyped(self, put_snap_config):
@patch("charms.operator_libs_linux.v2.snap.SnapClient._put_snap_conf")
def test_snap_set_untyped(self, put_snap_conf):
foo = snap.Snap("foo", snap.SnapState.Present, "stable", "1", "classic")

config = {"n": 42, "s": "string", "d": {"nested": True}}

foo.set(config, typed=False)
put_snap_config.assert_called_with(
put_snap_conf.assert_called_with(
"foo", {"n": "42", "s": "string", "d": "{'nested': True}"}
)

Expand Down

0 comments on commit b811c76

Please sign in to comment.