Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/markdown/podman-info.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ podman\-info - Display Podman related system information
## DESCRIPTION

Displays information pertinent to the host, current storage stats, configured container registries, and build of podman.
Host information includes configured CDI spec directories and resolved CDI devices when present.


## OPTIONS
Expand Down
8 changes: 8 additions & 0 deletions libpod/define/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type HostInfo struct {
CgroupManager string `json:"cgroupManager"`
CgroupsVersion string `json:"cgroupVersion"`
CgroupControllers []string `json:"cgroupControllers"`
CDISpecDirs []string `json:"cdiSpecDirs"`
DiscoveredDevices []DeviceInfo `json:"discoveredDevices,omitempty"`
Conmon *ConmonInfo `json:"conmon"`
CPUs int `json:"cpus"`
CPUUtilization *CPUUsage `json:"cpuUtilization"`
Expand Down Expand Up @@ -72,6 +74,12 @@ type HostInfo struct {
EmulatedArchitectures []string `json:"emulatedArchitectures,omitempty"`
}

// DeviceInfo describes a device discovered by a device source.
type DeviceInfo struct {
Source string `json:"source"`
ID string `json:"id"`
}

// RemoteSocket describes information about the API socket
type RemoteSocket struct {
Path string `json:"path,omitempty"`
Expand Down
29 changes: 29 additions & 0 deletions libpod/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go.podman.io/podman/v6/libpod/define"
"go.podman.io/podman/v6/libpod/linkmode"
"go.podman.io/storage/pkg/system"
"tags.cncf.io/container-device-interface/pkg/cdi"
)

// Info returns the store and host information
Expand Down Expand Up @@ -130,6 +131,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
SwapFree: mi.SwapFree,
SwapTotal: mi.SwapTotal,
}
info.CDISpecDirs, info.DiscoveredDevices = r.cdiInfo()
platform := parse.DefaultPlatform()
pArr := strings.Split(platform, "/")
if len(pArr) == 3 {
Expand Down Expand Up @@ -177,6 +179,33 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
return &info, nil
}

func (r *Runtime) cdiInfo() ([]string, []define.DeviceInfo) {
registry, err := cdi.NewCache(
cdi.WithSpecDirs(r.config.Engine.CdiSpecDirs.Get()...),
cdi.WithAutoRefresh(false),
)
if err != nil {
logrus.Debugf("Creating CDI registry for info: %v", err)
Comment thread
Honny1 marked this conversation as resolved.
return r.config.Engine.CdiSpecDirs.Get(), nil
}
if err := registry.Refresh(); err != nil {
logrus.Debugf("The following error was triggered when refreshing the CDI registry for info: %v", err)
}

return registry.GetSpecDirectories(), cdiDeviceInfo(registry.ListDevices())
}

func cdiDeviceInfo(deviceNames []string) []define.DeviceInfo {
devices := make([]define.DeviceInfo, 0, len(deviceNames))
for _, device := range deviceNames {
devices = append(devices, define.DeviceInfo{
Source: "cdi",
ID: device,
})
}
return devices
}

func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) {
var paused, running, stopped int
cs := define.ContainerStore{}
Expand Down
13 changes: 13 additions & 0 deletions pkg/api/handlers/compat/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
CPUSet: sysInfo.Cpuset,
CPUShares: sysInfo.CPUShares,
CgroupDriver: configInfo.Engine.CgroupManager,
CDISpecDirs: infoData.Host.CDISpecDirs,
ContainerdCommit: dockerSystem.Commit{},
Containers: infoData.Store.ContainerStore.Number,
ContainersPaused: stateInfo[define.ContainerStatePaused],
Expand All @@ -70,6 +71,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
Debug: log.IsLevelEnabled(log.DebugLevel),
DefaultAddressPools: getDefaultAddressPools(configInfo),
DefaultRuntime: configInfo.Engine.OCIRuntime,
DiscoveredDevices: getDiscoveredDevices(infoData.Host.DiscoveredDevices),
DockerRootDir: infoData.Store.GraphRoot,
Driver: infoData.Store.GraphDriverName,
DriverStatus: getGraphStatus(infoData.Store.GraphStatus),
Expand Down Expand Up @@ -132,6 +134,17 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, info)
}

func getDiscoveredDevices(discoveredDevices []define.DeviceInfo) []dockerSystem.DeviceInfo {
devices := make([]dockerSystem.DeviceInfo, 0, len(discoveredDevices))
for _, device := range discoveredDevices {
devices = append(devices, dockerSystem.DeviceInfo{
Source: device.Source,
ID: device.ID,
})
}
return devices
}

func getServiceConfig(runtime *libpod.Runtime) *registry.ServiceConfig {
var indexConfs map[string]*registry.IndexInfo

Expand Down
109 changes: 53 additions & 56 deletions test/e2e/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,16 @@ var _ = Describe("Podman Info", func() {
})

It("podman info --format GO template", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Store.GraphRoot}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
podmanTest.PodmanExitCleanly("info", "--format", "{{.Store.GraphRoot}}")
})

It("podman info --format GO template", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Registries}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Registries}}")
Expect(session.OutputToString()).To(ContainSubstring("registry"))
})

It("podman info --format GO template plugins", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Plugins}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Plugins}}")
Expect(session.OutputToString()).To(ContainSubstring("local"))
Expect(session.OutputToString()).To(ContainSubstring("journald"))
Expect(session.OutputToString()).To(ContainSubstring("bridge"))
Expand All @@ -72,10 +66,7 @@ var _ = Describe("Podman Info", func() {
SkipIfNotRootless("test of rootless_storage_path is only meaningful as rootless")
SkipIfRemote("Only tests storage on local client")
configPath := filepath.Join(podmanTest.TempDir, ".config", "containers", "storage.conf")
os.Setenv("CONTAINERS_STORAGE_CONF", configPath)
defer func() {
os.Unsetenv("CONTAINERS_STORAGE_CONF")
}()
GinkgoT().Setenv("CONTAINERS_STORAGE_CONF", configPath)
err := os.RemoveAll(filepath.Dir(configPath))
Expect(err).ToNot(HaveOccurred())

Expand Down Expand Up @@ -104,38 +95,30 @@ var _ = Describe("Podman Info", func() {
})

It("check RemoteSocket ", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.RemoteSocket.Path}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.RemoteSocket.Path}}")
switch podmanTest.RemoteSocketScheme {
case "unix":
Expect(session.OutputToString()).To(MatchRegexp("/run/.*podman.*sock"))
case "tcp":
Expect(session.OutputToString()).To(MatchRegexp("tcp://127.0.0.1:.*"))
}

session = podmanTest.Podman([]string{"info", "--format", "{{.Host.ServiceIsRemote}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.ServiceIsRemote}}")
if podmanTest.RemoteTest {
Expect(session.OutputToString()).To(Equal("true"))
} else {
Expect(session.OutputToString()).To(Equal("false"))
}

if IsRemote() {
session = podmanTest.Podman([]string{"info", "--format", "{{.Host.RemoteSocket.Exists}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.RemoteSocket.Exists}}")
Expect(session.OutputToString()).To(Equal("true"))
}
})

It("Podman info must contain cgroupControllers with RelevantControllers", func() {
SkipIfRootless("Hard to tell which controllers are going to be enabled for rootless")
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.CgroupControllers}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.CgroupControllers}}")
Expect(session.OutputToString()).To(ContainSubstring("memory"))
Expect(session.OutputToString()).To(ContainSubstring("pids"))
})
Expand All @@ -149,28 +132,22 @@ var _ = Describe("Podman Info", func() {
}
Fail("CIRRUS_CI is set, but CI_DESIRED_RUNTIME is not! See #14912")
}
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.OCIRuntime.Name}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.OCIRuntime.Name}}")
Expect(session.OutputToString()).To(Equal(want))
})

It("Podman info: check desired network backend", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackend}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackend}}")
Expect(session.OutputToString()).To(Equal("netavark"))

session = podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackendInfo.Backend}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackendInfo.Backend}}")
Expect(session.OutputToString()).To(Equal("netavark"))
})

It("Podman info: check default network from configuration", func() {
configPath := filepath.Join(podmanTest.TempDir, "containers.conf")

os.Setenv("CONTAINERS_CONF_OVERRIDE", configPath)
GinkgoT().Setenv("CONTAINERS_CONF_OVERRIDE", configPath)

customNetName := "my-custom-test-network"
configContent := fmt.Sprintf("[network]\ndefault_network=%q\n", customNetName)
Expand All @@ -179,12 +156,44 @@ var _ = Describe("Podman Info", func() {
Expect(err).ToNot(HaveOccurred())
podmanTest.RestartRemoteService()

session := podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackendInfo.DefaultNetwork}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackendInfo.DefaultNetwork}}")
Expect(session.OutputToString()).To(Equal(customNetName))
})

It("Podman info: check CDI spec dirs and devices from configuration", func() {
cdiDir := filepath.Join(podmanTest.TempDir, "cdi")
err := os.MkdirAll(cdiDir, os.ModePerm)
Expect(err).ToNot(HaveOccurred())

cdiSpec := []byte(`{
"cdiVersion": "0.3.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myKmsg",
"containerEdits": {
"env": ["PODMAN_CDI_INFO_TEST=1"]
}
}
]
}`)
err = os.WriteFile(filepath.Join(cdiDir, "device.json"), cdiSpec, os.ModePerm)
Expect(err).ToNot(HaveOccurred())

configPath := filepath.Join(podmanTest.TempDir, "containers.conf")
configContent := fmt.Sprintf("[engine]\ncdi_spec_dirs = [%q]\n", cdiDir)
err = os.WriteFile(configPath, []byte(configContent), os.ModePerm)
Expect(err).ToNot(HaveOccurred())

GinkgoT().Setenv("CONTAINERS_CONF_OVERRIDE", configPath)
podmanTest.RestartRemoteService()

session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.CDISpecDirs}} {{.Host.DiscoveredDevices}}")

Expect(session.OutputToString()).To(ContainSubstring(cdiDir))
Expect(session.OutputToString()).To(ContainSubstring("vendor.com/device=myKmsg"))
})

It("Podman info: check desired storage driver", func() {
// defined in .cirrus.yml
want := os.Getenv("CI_DESIRED_STORAGE")
Expand All @@ -194,9 +203,7 @@ var _ = Describe("Podman Info", func() {
}
Fail("CIRRUS_CI is set, but CI_DESIRED_STORAGE is not! See #20161")
}
session := podmanTest.Podman([]string{"info", "--format", "{{.Store.GraphDriverName}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Store.GraphDriverName}}")
Expect(session.OutputToString()).To(Equal(want), ".Store.GraphDriverName from podman info")

// Confirm desired setting of composefs
Expand All @@ -205,9 +212,7 @@ var _ = Describe("Podman Info", func() {
if os.Getenv("CI_DESIRED_COMPOSEFS") != "" {
expect = "true"
}
session = podmanTest.Podman([]string{"info", "--format", `{{index .Store.GraphOptions "overlay.use_composefs"}}`})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", `{{index .Store.GraphOptions "overlay.use_composefs"}}`)
Expect(session.OutputToString()).To(Equal(expect), ".Store.GraphOptions -> overlay.use_composefs")
}
})
Expand All @@ -216,19 +221,13 @@ var _ = Describe("Podman Info", func() {
// This should not run on architectures and OSes that use the file locks backend.
// Which, for now, is Linux + RISCV and FreeBSD, neither of which are in CI - so
// no skips.
info1 := podmanTest.Podman([]string{"info", "--format", "{{ .Host.FreeLocks }}"})
info1.WaitWithDefaultTimeout()
Expect(info1).To(ExitCleanly())
info1 := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Host.FreeLocks }}")
free1, err := strconv.Atoi(info1.OutputToString())
Expect(err).To(Not(HaveOccurred()))

ctr := podmanTest.Podman([]string{"create", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).To(ExitCleanly())
podmanTest.PodmanExitCleanly("create", ALPINE, "top")

info2 := podmanTest.Podman([]string{"info", "--format", "{{ .Host.FreeLocks }}"})
info2.WaitWithDefaultTimeout()
Expect(info2).To(ExitCleanly())
info2 := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Host.FreeLocks }}")
free2, err := strconv.Atoi(info2.OutputToString())
Expect(err).To(Not(HaveOccurred()))

Expand All @@ -251,9 +250,7 @@ var _ = Describe("Podman Info", func() {
})

It("Podman info: check client information", func() {
info := podmanTest.Podman([]string{"info", "--format", "{{ .Client }}"})
info.WaitWithDefaultTimeout()
Expect(info).To(ExitCleanly())
info := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Client }}")
// client info should only appear when using the remote client
if IsRemote() {
Expect(info.OutputToString()).ToNot(Equal("<nil>"))
Expand Down
31 changes: 31 additions & 0 deletions test/system/005-info.bats
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,37 @@ store.imageStore.number | 1
done < <(parse_table "$tests")
}

@test "podman info - CDI spec dirs and devices" {
skip_if_remote "--cdi-spec-dir flag is not supported for remote"

cdi_dir=$PODMAN_TMPDIR/cdi
mkdir -p "$cdi_dir"
cat >"$cdi_dir/device.json" <<EOF
{
"cdiVersion": "0.3.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myKmsg",
"containerEdits": {
"env": ["PODMAN_CDI_INFO_TEST=1"]
}
}
]
}
EOF

run_podman --cdi-spec-dir "$cdi_dir" info --format=json
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this not essentially the same test as in e2e except that this one uses --cdi-spec-dir while the e2e one uses the config file?

personally I like to avoid test duplication between the two as it just adds unnecessary CI time, our tests are already slow

Feels like it would be simpler to just add this into the e2e test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this change is probably not strictly required since we're not checking anything other than the json content reported by the info command.

My one naive question would be how this is different to the other tests that are also added here?

(Happy to remove this if you feel strongly about it though).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don't think the --cdi-spec-dir flag is in common use, and if I recall correctly it was mainly added for testing flexibility.

cdi_spec_dirs=$(echo "$output" | jq -r '.host.cdiSpecDirs[]')
cdi_devices=$(echo "$output" | jq -r '.host.discoveredDevices[] | select(.source == "cdi") | .id')
assert "$cdi_spec_dirs" =~ "$cdi_dir" "info includes configured CDI spec dir"
assert "$cdi_devices" =~ "vendor.com/device=myKmsg" "info includes resolved CDI device"

run_podman --cdi-spec-dir "$cdi_dir" system info --format '{{.Host.CDISpecDirs}} {{.Host.DiscoveredDevices}}'
assert "$output" =~ "$cdi_dir" "system info includes configured CDI spec dir"
assert "$output" =~ "vendor.com/device=myKmsg" "system info includes resolved CDI device"
}

@test "podman info - confirm desired runtime" {
if [[ -z "$CI_DESIRED_RUNTIME" ]]; then
# When running in Cirrus, CI_DESIRED_RUNTIME *must* be defined
Expand Down