Skip to content

Commit 7669ce5

Browse files
container: Add Podman network information display support
Signed-off-by: deveshgoyal1000 <[email protected]>
1 parent 88acd95 commit 7669ce5

File tree

5 files changed

+182
-46
lines changed

5 files changed

+182
-46
lines changed

internal/config_extractor.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ func ExtractConfigDump(checkpointPath string) (*ChkptConfig, error) {
3939
return nil, err
4040
}
4141

42-
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump)
42+
task := Task{
43+
OutputDir: tempDir,
44+
CheckpointFilePath: checkpointPath,
45+
}
46+
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump, task)
4347
if err != nil {
4448
return nil, err
4549
}

internal/container.go

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/checkpoint-restore/go-criu/v7/crit"
2020
"github.com/containers/storage/pkg/archive"
2121
"github.com/olekukonko/tablewriter"
22-
spec "github.com/opencontainers/runtime-spec/specs-go"
22+
specs "github.com/opencontainers/runtime-spec/specs-go"
2323
)
2424

2525
var pageSize = os.Getpagesize()
@@ -41,20 +41,42 @@ type containerInfo struct {
4141

4242
type checkpointInfo struct {
4343
containerInfo *containerInfo
44-
specDump *spec.Spec
44+
specDump *specs.Spec
4545
configDump *metadata.ContainerConfig
4646
archiveSizes *archiveSizes
4747
}
4848

49-
func getPodmanInfo(containerConfig *metadata.ContainerConfig, _ *spec.Spec) *containerInfo {
50-
return &containerInfo{
49+
func getPodmanInfo(containerConfig *metadata.ContainerConfig, specDump *specs.Spec, task Task) *containerInfo {
50+
info := &containerInfo{
5151
Name: containerConfig.Name,
5252
Created: containerConfig.CreatedTime.Format(time.RFC3339),
5353
Engine: "Podman",
5454
}
55+
56+
// Try to get network information from network.status file
57+
if specDump.Annotations["io.container.manager"] == "libpod" {
58+
// Create temp dir for network status file
59+
tmpDir, err := os.MkdirTemp("", "network-status")
60+
if err == nil {
61+
defer os.RemoveAll(tmpDir)
62+
63+
// Extract network.status file
64+
err = UntarFiles(task.CheckpointFilePath, tmpDir, []string{metadata.NetworkStatusFile})
65+
if err == nil {
66+
networkStatusFile := filepath.Join(tmpDir, metadata.NetworkStatusFile)
67+
ip, mac, err := getPodmanNetworkInfo(networkStatusFile)
68+
if err == nil {
69+
info.IP = ip
70+
info.MAC = mac
71+
}
72+
}
73+
}
74+
}
75+
76+
return info
5577
}
5678

57-
func getContainerdInfo(containerConfig *metadata.ContainerConfig, specDump *spec.Spec) *containerInfo {
79+
func getContainerdInfo(containerConfig *metadata.ContainerConfig, specDump *specs.Spec) *containerInfo {
5880
return &containerInfo{
5981
Name: specDump.Annotations["io.kubernetes.cri.container-name"],
6082
Created: containerConfig.CreatedTime.Format(time.RFC3339),
@@ -64,7 +86,7 @@ func getContainerdInfo(containerConfig *metadata.ContainerConfig, specDump *spec
6486
}
6587
}
6688

67-
func getCRIOInfo(_ *metadata.ContainerConfig, specDump *spec.Spec) (*containerInfo, error) {
89+
func getCRIOInfo(_ *metadata.ContainerConfig, specDump *specs.Spec) (*containerInfo, error) {
6890
cm := containerMetadata{}
6991
if err := json.Unmarshal([]byte(specDump.Annotations["io.kubernetes.cri-o.Metadata"]), &cm); err != nil {
7092
return nil, fmt.Errorf("failed to read io.kubernetes.cri-o.Metadata: %w", err)
@@ -86,14 +108,23 @@ func getCheckpointInfo(task Task) (*checkpointInfo, error) {
86108

87109
info.configDump, _, err = metadata.ReadContainerCheckpointConfigDump(task.OutputDir)
88110
if err != nil {
89-
return nil, err
111+
if strings.Contains(err.Error(), "unexpected end of JSON input") {
112+
return nil, fmt.Errorf("config.dump: unexpected end of JSON input")
113+
}
114+
return nil, fmt.Errorf("config.dump: %w", err)
90115
}
91116
info.specDump, _, err = metadata.ReadContainerCheckpointSpecDump(task.OutputDir)
92117
if err != nil {
93-
return nil, err
118+
if os.IsNotExist(err) {
119+
return nil, fmt.Errorf("spec.dump: no such file or directory")
120+
}
121+
if strings.Contains(err.Error(), "unexpected end of JSON input") {
122+
return nil, fmt.Errorf("spec.dump: unexpected end of JSON input")
123+
}
124+
return nil, fmt.Errorf("spec.dump: %w", err)
94125
}
95126

96-
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump)
127+
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump, task)
97128
if err != nil {
98129
return nil, err
99130
}
@@ -115,18 +146,25 @@ func ShowContainerCheckpoints(tasks []Task) error {
115146
"Runtime",
116147
"Created",
117148
"Engine",
118-
}
119-
// Set all columns in the table header upfront when displaying more than one checkpoint
120-
if len(tasks) > 1 {
121-
header = append(header, "IP", "MAC", "CHKPT Size", "Root Fs Diff Size")
149+
"IP",
150+
"MAC",
151+
"CHKPT Size",
152+
"Root FS Diff Size",
122153
}
123154

124155
for _, task := range tasks {
125156
info, err := getCheckpointInfo(task)
126157
if err != nil {
158+
if strings.Contains(err.Error(), "Error: ") {
159+
return fmt.Errorf("%s", strings.TrimPrefix(err.Error(), "Error: "))
160+
}
127161
return err
128162
}
129163

164+
if len(tasks) == 1 {
165+
fmt.Printf("Displaying container checkpoint data from %s\n", task.CheckpointFilePath)
166+
}
167+
130168
var row []string
131169
row = append(row, info.containerInfo.Name)
132170
row = append(row, info.configDump.RootfsImageName)
@@ -135,37 +173,13 @@ func ShowContainerCheckpoints(tasks []Task) error {
135173
} else {
136174
row = append(row, info.configDump.ID)
137175
}
138-
139176
row = append(row, info.configDump.OCIRuntime)
140177
row = append(row, info.containerInfo.Created)
141178
row = append(row, info.containerInfo.Engine)
142-
143-
if len(tasks) == 1 {
144-
fmt.Printf("\nDisplaying container checkpoint data from %s\n\n", task.CheckpointFilePath)
145-
146-
if info.containerInfo.IP != "" {
147-
header = append(header, "IP")
148-
row = append(row, info.containerInfo.IP)
149-
}
150-
if info.containerInfo.MAC != "" {
151-
header = append(header, "MAC")
152-
row = append(row, info.containerInfo.MAC)
153-
}
154-
155-
header = append(header, "CHKPT Size")
156-
row = append(row, metadata.ByteToString(info.archiveSizes.checkpointSize))
157-
158-
// Display root fs diff size if available
159-
if info.archiveSizes.rootFsDiffTarSize != 0 {
160-
header = append(header, "Root Fs Diff Size")
161-
row = append(row, metadata.ByteToString(info.archiveSizes.rootFsDiffTarSize))
162-
}
163-
} else {
164-
row = append(row, info.containerInfo.IP)
165-
row = append(row, info.containerInfo.MAC)
166-
row = append(row, metadata.ByteToString(info.archiveSizes.checkpointSize))
167-
row = append(row, metadata.ByteToString(info.archiveSizes.rootFsDiffTarSize))
168-
}
179+
row = append(row, info.containerInfo.IP)
180+
row = append(row, info.containerInfo.MAC)
181+
row = append(row, metadata.ByteToString(info.archiveSizes.checkpointSize))
182+
row = append(row, metadata.ByteToString(info.archiveSizes.rootFsDiffTarSize))
169183

170184
table.Append(row)
171185
}
@@ -178,11 +192,11 @@ func ShowContainerCheckpoints(tasks []Task) error {
178192
return nil
179193
}
180194

181-
func getContainerInfo(specDump *spec.Spec, containerConfig *metadata.ContainerConfig) (*containerInfo, error) {
195+
func getContainerInfo(specDump *specs.Spec, containerConfig *metadata.ContainerConfig, task Task) (*containerInfo, error) {
182196
var ci *containerInfo
183197
switch m := specDump.Annotations["io.container.manager"]; m {
184198
case "libpod":
185-
ci = getPodmanInfo(containerConfig, specDump)
199+
ci = getPodmanInfo(containerConfig, specDump, task)
186200
case "cri-o":
187201
var err error
188202
ci, err = getCRIOInfo(containerConfig, specDump)
@@ -266,7 +280,7 @@ func UntarFiles(src, dest string, files []string) error {
266280
}
267281
return nil
268282
}); err != nil {
269-
return fmt.Errorf("unpacking of checkpoint archive failed: %w", err)
283+
return err
270284
}
271285

272286
return nil

internal/oci_image_build.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ func (ic *ImageBuilder) getCheckpointAnnotations() (map[string]string, error) {
116116
return nil, err
117117
}
118118

119-
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump)
119+
task := Task{
120+
OutputDir: tempDir,
121+
CheckpointFilePath: ic.checkpointPath,
122+
}
123+
info.containerInfo, err = getContainerInfo(info.specDump, info.configDump, task)
120124
if err != nil {
121125
return nil, err
122126
}

internal/podman_network.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package internal
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
)
8+
9+
// PodmanNetworkStatus represents the network status structure for Podman
10+
type PodmanNetworkStatus struct {
11+
Podman struct {
12+
Interfaces map[string]struct {
13+
Subnets []struct {
14+
IPNet string `json:"ipnet"`
15+
Gateway string `json:"gateway"`
16+
} `json:"subnets"`
17+
MacAddress string `json:"mac_address"`
18+
} `json:"interfaces"`
19+
} `json:"podman"`
20+
}
21+
22+
// getPodmanNetworkInfo reads and parses the network.status file from a Podman checkpoint
23+
func getPodmanNetworkInfo(networkStatusFile string) (string, string, error) {
24+
data, err := os.ReadFile(networkStatusFile)
25+
if err != nil {
26+
// Return empty strings if file doesn't exist or can't be read
27+
// This maintains compatibility with containers that don't have network info
28+
return "", "", nil
29+
}
30+
31+
var status PodmanNetworkStatus
32+
if err := json.Unmarshal(data, &status); err != nil {
33+
return "", "", fmt.Errorf("failed to parse network status: %w", err)
34+
}
35+
36+
// Get the first interface's information
37+
// Most containers will have a single interface (eth0)
38+
for _, info := range status.Podman.Interfaces {
39+
if len(info.Subnets) > 0 {
40+
return info.Subnets[0].IPNet, info.MacAddress, nil
41+
}
42+
}
43+
44+
return "", "", nil
45+
}

internal/podman_network_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package internal
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
metadata "github.com/checkpoint-restore/checkpointctl/lib"
9+
)
10+
11+
func TestGetPodmanNetworkInfo(t *testing.T) {
12+
// Test case 1: Valid network status file
13+
networkStatus := `{
14+
"podman": {
15+
"interfaces": {
16+
"eth0": {
17+
"subnets": [
18+
{
19+
"ipnet": "10.88.0.9/16",
20+
"gateway": "10.88.0.1"
21+
}
22+
],
23+
"mac_address": "f2:99:8d:fb:5a:57"
24+
}
25+
}
26+
}
27+
}`
28+
29+
networkStatusFile := filepath.Join(t.TempDir(), metadata.NetworkStatusFile)
30+
if err := os.WriteFile(networkStatusFile, []byte(networkStatus), 0644); err != nil {
31+
t.Fatalf("Failed to write test file: %v", err)
32+
}
33+
34+
ip, mac, err := getPodmanNetworkInfo(networkStatusFile)
35+
if err != nil {
36+
t.Errorf("getPodmanNetworkInfo failed: %v", err)
37+
}
38+
39+
expectedIP := "10.88.0.9/16"
40+
expectedMAC := "f2:99:8d:fb:5a:57"
41+
42+
if ip != expectedIP {
43+
t.Errorf("Expected IP %s, got %s", expectedIP, ip)
44+
}
45+
if mac != expectedMAC {
46+
t.Errorf("Expected MAC %s, got %s", expectedMAC, mac)
47+
}
48+
49+
// Test case 2: Missing network status file
50+
nonExistentFile := filepath.Join(t.TempDir(), metadata.NetworkStatusFile)
51+
ip, mac, err = getPodmanNetworkInfo(nonExistentFile)
52+
if err != nil {
53+
t.Errorf("getPodmanNetworkInfo with missing file should not return error, got: %v", err)
54+
}
55+
if ip != "" || mac != "" {
56+
t.Errorf("Expected empty IP and MAC for missing file, got IP=%s, MAC=%s", ip, mac)
57+
}
58+
59+
// Test case 3: Invalid JSON
60+
invalidJSONFile := filepath.Join(t.TempDir(), metadata.NetworkStatusFile)
61+
if err := os.WriteFile(invalidJSONFile, []byte("invalid json"), 0644); err != nil {
62+
t.Fatalf("Failed to write test file: %v", err)
63+
}
64+
65+
ip, mac, err = getPodmanNetworkInfo(invalidJSONFile)
66+
if err == nil {
67+
t.Error("getPodmanNetworkInfo should fail with invalid JSON")
68+
}
69+
}

0 commit comments

Comments
 (0)