Skip to content

Commit

Permalink
client,daemon: expose features supported/enabled in /v2/system-info
Browse files Browse the repository at this point in the history
Add a "features" field containing a map of feature flag names to boolean
subfields for whether the feature is "supported" and/or "enabled".

Feature flags which are not set to true or false are omitted from this
map. Feature flags may be unsupported but nonetheless enabled. This
indicates that the feature flag has been set to true, but the backing
feature itself is not currently supported.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
  • Loading branch information
olivercalder committed Mar 9, 2024
1 parent 685800b commit 0eb480f
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 5 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* state: add support for notices (from pebble)
* daemon: add notices to the snapd API under `/v2/notices` and `/v2/notice`
* Mandatory device cgroup for all snaps using bare and core24 base as well as future bases
* client,daemon: expose features supported/enabled in `/v2/system-info`

# New in snapd 2.61.3:
* Install systemd files in correct location for 24.04
Expand Down
5 changes: 4 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2015-2020 Canonical Ltd
* Copyright (C) 2015-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -36,6 +36,7 @@ import (
"time"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/jsonutil"
)
Expand Down Expand Up @@ -688,6 +689,8 @@ type SysInfo struct {
Refresh RefreshInfo `json:"refresh,omitempty"`
Confinement string `json:"confinement"`
SandboxFeatures map[string][]string `json:"sandbox-features,omitempty"`

Features map[string]features.FeatureInfo `json:"features,omitempty"`
}

func (rsp *response) err(cli *Client, statusCode int) error {
Expand Down
15 changes: 13 additions & 2 deletions client/client_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2015-2016 Canonical Ltd
* Copyright (C) 2015-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -39,6 +39,7 @@ import (

"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/testutil"
)
Expand Down Expand Up @@ -359,7 +360,11 @@ func (cs *clientSuite) TestClientSysInfo(c *C) {
"confinement": "strict",
"architecture": "TI-99/4A",
"virtualization": "MESS",
"sandbox-features": {"backend": ["feature-1", "feature-2"]}}}`
"sandbox-features": {"backend": ["feature-1", "feature-2"]},
"features": {"foo": {"supported": false, "enabled": false},
"bar": {"supported": false, "enabled": true},
"baz": {"supported": true, "enabled": false},
"buzz": {"supported": true, "enabled": true}}}}`
sysInfo, err := cs.cli.SysInfo()
c.Check(err, IsNil)
c.Check(sysInfo, DeepEquals, &client.SysInfo{
Expand All @@ -377,6 +382,12 @@ func (cs *clientSuite) TestClientSysInfo(c *C) {
BuildID: "1234",
Architecture: "TI-99/4A",
Virtualization: "MESS",
Features: map[string]features.FeatureInfo{
"foo": {Supported: false, Enabled: false},
"bar": {Supported: false, Enabled: true},
"baz": {Supported: true, Enabled: false},
"buzz": {Supported: true, Enabled: true},
},
})
}

Expand Down
6 changes: 5 additions & 1 deletion daemon/api_general.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2015-2020 Canonical Ltd
* Copyright (C) 2015-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -30,10 +30,12 @@ import (
"github.com/snapcore/snapd/arch"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
Expand Down Expand Up @@ -105,6 +107,7 @@ func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response {
deviceMgr := c.d.overlord.DeviceManager()
st.Lock()
defer st.Unlock()
tr := config.NewTransaction(st)
nextRefresh := snapMgr.NextRefresh()
lastRefresh, _ := snapMgr.LastRefresh()
refreshHold, _ := snapMgr.EffectiveRefreshHold()
Expand Down Expand Up @@ -143,6 +146,7 @@ func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response {
"refresh": refreshInfo,
"architecture": arch.DpkgArchitecture(),
"system-mode": deviceMgr.SystemMode(devicestate.SysAny),
"features": features.All(tr),
}
if systemdVirt != "" {
m["virtualization"] = systemdVirt
Expand Down
47 changes: 46 additions & 1 deletion daemon/api_general_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2020 Canonical Ltd
* Copyright (C) 2014-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -35,12 +35,14 @@ import (
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/daemon"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/sandbox"
"github.com/snapcore/snapd/systemd"
)

var _ = check.Suite(&generalSuite{})
Expand Down Expand Up @@ -90,6 +92,8 @@ func (s *generalSuite) TestSysInfo(c *check.C) {
tr := config.NewTransaction(st)
tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00")
tr.Set("core", "refresh.timer", "8:00~9:00/2")
tr.Set("core", "experimental.parallel-instances", "false")
tr.Set("core", "experimental.quota-groups", "true")
tr.Commit()
st.Unlock()

Expand All @@ -103,6 +107,9 @@ func (s *generalSuite) TestSysInfo(c *check.C) {
dirs.SetRootDir(dirs.GlobalRootDir)
restore = daemon.MockSystemdVirt("magic")
defer restore()
// Set systemd version <230 so QuotaGroups feature unsupported
restore = systemd.MockSystemdVersion(229, nil)
defer restore()

buildID := "this-is-my-build-id"
restore = daemon.MockBuildID(buildID)
Expand Down Expand Up @@ -145,7 +152,41 @@ func (s *generalSuite) TestSysInfo(c *check.C) {
const kernelVersionKey = "kernel-version"
c.Check(rsp.Result.(map[string]interface{})[kernelVersionKey], check.Not(check.Equals), "")
delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
// Extract "features" field and remove it from result; check it later.
const featuresKey = "features"
resultFeatures := rsp.Result.(map[string]interface{})[featuresKey]
c.Check(resultFeatures, check.Not(check.Equals), "")
delete(rsp.Result.(map[string]interface{}), featuresKey)

c.Check(rsp.Result, check.DeepEquals, expected)

// Check that "features" is map
featuresAll, ok := resultFeatures.(map[string]interface{})
c.Assert(ok, check.Equals, true)
// Ensure that Layouts exists and is feature.FeatureInfo
layoutsInfoRaw, exists := featuresAll[features.Layouts.String()]
c.Assert(exists, check.Equals, true)
layoutsInfo, ok := layoutsInfoRaw.(map[string]interface{})
c.Assert(ok, check.Equals, true, check.Commentf("%+v", layoutsInfoRaw))
// Ensure that Layouts is supported and enabled
c.Check(layoutsInfo["supported"], check.Equals, true)
c.Check(layoutsInfo["enabled"], check.Equals, true)
// Ensure that ParallelInstances exists and is a feature.FeatureInfo
parallelInstancesInfoRaw, exists := featuresAll[features.ParallelInstances.String()]
c.Assert(exists, check.Equals, true)
parallelInstancesInfo, ok := parallelInstancesInfoRaw.(map[string]interface{})
c.Assert(ok, check.Equals, true)
// Ensure that ParallelInstances is supported and not enabled
c.Check(parallelInstancesInfo["supported"], check.Equals, true)
c.Check(parallelInstancesInfo["enabled"], check.Equals, false)
// Ensure that QuotaGroups exists and is a feature.FeatureInfo
quotaGroupsInfoRaw, exists := featuresAll[features.QuotaGroups.String()]
c.Assert(exists, check.Equals, true)
quotaGroupsInfo, ok := quotaGroupsInfoRaw.(map[string]interface{})
c.Assert(ok, check.Equals, true)
// Ensure that QuotaGroups is unsupported but enabled
c.Check(quotaGroupsInfo["supported"], check.Equals, false)
c.Check(quotaGroupsInfo["enabled"], check.Equals, true)
}

func (s *generalSuite) TestSysInfoLegacyRefresh(c *check.C) {
Expand Down Expand Up @@ -224,6 +265,8 @@ func (s *generalSuite) TestSysInfoLegacyRefresh(c *check.C) {
c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
const kernelVersionKey = "kernel-version"
delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
const featuresKey = "features"
delete(rsp.Result.(map[string]interface{}), featuresKey)
c.Check(rsp.Result, check.DeepEquals, expected)
}

Expand Down Expand Up @@ -302,6 +345,8 @@ func (s *generalSuite) testSysInfoSystemMode(c *check.C, mode string) {
c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
const kernelVersionKey = "kernel-version"
delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
const featuresKey = "features"
delete(rsp.Result.(map[string]interface{}), featuresKey)
c.Check(rsp.Result, check.DeepEquals, expected)
}

Expand Down

0 comments on commit 0eb480f

Please sign in to comment.