From 2ab76bc4afec6b6cab13448bf1bd9bd5da227b8e Mon Sep 17 00:00:00 2001 From: Zeyad Gouda Date: Mon, 7 Oct 2024 12:03:30 +0300 Subject: [PATCH] daemon: attach refresh failure information to /v2/snaps Signed-off-by: Zeyad Gouda --- client/clientutil/snapinfo_test.go | 1 + client/packages.go | 2 ++ client/packages_test.go | 10 ++++++++++ daemon/api_snaps_test.go | 32 ++++++++++++++++++++++++++++++ daemon/snap.go | 1 + 5 files changed, 46 insertions(+) diff --git a/client/clientutil/snapinfo_test.go b/client/clientutil/snapinfo_test.go index 96f43c11a43..c42361d9f2a 100644 --- a/client/clientutil/snapinfo_test.go +++ b/client/clientutil/snapinfo_test.go @@ -121,6 +121,7 @@ func (*cmdSuite) TestClientSnapFromSnapInfo(c *C) { "Hold", "GatingHold", "RefreshInhibit", + "RefreshFailures", "Components", } var checker func(string, reflect.Value) diff --git a/client/packages.go b/client/packages.go index d4a9c4cd0f0..8b5d3c2c93b 100644 --- a/client/packages.go +++ b/client/packages.go @@ -91,6 +91,8 @@ type Snap struct { GatingHold *time.Time `json:"gating-hold,omitempty"` // if RefreshInhibit is nil, then there is no pending refresh. RefreshInhibit *SnapRefreshInhibit `json:"refresh-inhibit,omitempty"` + // RefreshFailures tracks information about snap failed refreshes. + RefreshFailures *snap.RefreshFailuresInfo `json:"refresh-failures,omitempty"` // Components is a list of the snap components Components []Component `json:"components,omitempty"` diff --git a/client/packages_test.go b/client/packages_test.go index bd89e63eabc..d6c248999b2 100644 --- a/client/packages_test.go +++ b/client/packages_test.go @@ -274,6 +274,11 @@ func (cs *clientSuite) testClientSnap(c *check.C, refreshInhibited bool) { "private": true, "devmode": true, "trymode": true, + "refresh-failures": { + "to-revision": 43, + "failure-count": 5, + "last-failure-time": "2024-10-06T21:31:05Z" + }, "screenshots": [ {"url":"http://example.com/shot1.png", "width":640, "height":480}, {"url":"http://example.com/shot2.png"} @@ -356,6 +361,11 @@ func (cs *clientSuite) testClientSnap(c *check.C, refreshInhibited bool) { Website: "http://example.com/funky", StoreURL: "https://snapcraft.io/chatroom", RefreshInhibit: expectedSnapRefreshInhibit, + RefreshFailures: &snap.RefreshFailuresInfo{ + ToRevision: snap.R(43), + FailureCount: 5, + LastFailureTime: time.Date(2024, 10, 6, 21, 31, 5, 0, time.UTC), + }, }) } diff --git a/daemon/api_snaps_test.go b/daemon/api_snaps_test.go index 3ac987ab0e1..2a8ef4c68ed 100644 --- a/daemon/api_snaps_test.go +++ b/daemon/api_snaps_test.go @@ -1578,6 +1578,38 @@ func (s *snapsSuite) TestSnapManyInfosSelectRefreshInhibited(c *check.C) { } } +func (s *snapsSuite) TestSnapInfoReturnsRefreshFailures(c *check.C) { + s.expectSnapsNameReadAccess() + d := s.daemon(c) + s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), true, "") + + expectedRefreshFailures := &snap.RefreshFailuresInfo{ + ToRevision: snap.R(6), + FailureCount: 4, + LastFailureTime: time.Date(2024, time.October, 10, 21, 22, 11, 0, time.UTC), + } + + st := d.Overlord().State() + st.Lock() + var snapst snapstate.SnapState + // Update snap state with RefreshFailure. + c.Assert(snapstate.Get(st, "foo", &snapst), check.IsNil) + snapst.RefreshFailures = expectedRefreshFailures + snapstate.Set(st, "foo", &snapst) + st.Unlock() + + req, err := http.NewRequest("GET", "/v2/snaps/foo", nil) + c.Assert(err, check.IsNil) + + rsp := s.syncReq(c, req, nil) + + c.Assert(rsp.Result, check.FitsTypeOf, &client.Snap{}) + snapInfo := rsp.Result.(*client.Snap) + c.Assert(snapInfo.RefreshFailures, check.NotNil) + + c.Check(snapInfo.RefreshFailures, check.DeepEquals, expectedRefreshFailures) +} + func (s *snapsSuite) TestMapLocalFields(c *check.C) { media := snap.MediaInfos{ { diff --git a/daemon/snap.go b/daemon/snap.go index 3f2cd589bb7..91404bb9904 100644 --- a/daemon/snap.go +++ b/daemon/snap.go @@ -256,6 +256,7 @@ func mapLocal(about aboutSnap, sd clientutil.StatusDecorator) *client.Snap { result.DevMode = snapst.DevMode result.TryMode = snapst.TryMode result.JailMode = snapst.JailMode + result.RefreshFailures = snapst.RefreshFailures result.MountedFrom = localSnap.MountFile() if result.TryMode { // Readlink instead of EvalSymlinks because it's only expected