Browse code

integration/container: migrate TestAPIStatsNetworkStats to integration suite

Signed-off-by: Daniel Villavicencio <dvm3099@pm.me>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Daniel Villavicencio authored on 2026/03/30 05:50:51
Showing 2 changed files
... ...
@@ -6,9 +6,6 @@ import (
6 6
 	"fmt"
7 7
 	"io"
8 8
 	"net/http"
9
-	"os/exec"
10
-	"runtime"
11
-	"strconv"
12 9
 	"strings"
13 10
 	"testing"
14 11
 	"time"
... ...
@@ -99,83 +96,6 @@ func (s *DockerAPISuite) TestAPIStatsStoppedContainerInGoroutines(c *testing.T)
99 99
 	}
100 100
 }
101 101
 
102
-func (s *DockerAPISuite) TestAPIStatsNetworkStats(c *testing.T) {
103
-	skip.If(c, RuntimeIsWindowsContainerd(), "FIXME: Broken on Windows + containerd combination")
104
-	testRequires(c, testEnv.IsLocalDaemon)
105
-
106
-	id := runSleepingContainer(c)
107
-	cli.WaitRun(c, id)
108
-
109
-	// Retrieve the container address
110
-	net := "bridge"
111
-	if testEnv.DaemonInfo.OSType == "windows" {
112
-		net = "nat"
113
-	}
114
-	contIP := findContainerIP(c, id, net)
115
-	numPings := 1
116
-
117
-	var preRxPackets uint64
118
-	var preTxPackets uint64
119
-	var postRxPackets uint64
120
-	var postTxPackets uint64
121
-
122
-	// Get the container networking stats before and after pinging the container
123
-	nwStatsPre := getNetworkStats(c, id)
124
-	for _, v := range nwStatsPre {
125
-		preRxPackets += v.RxPackets
126
-		preTxPackets += v.TxPackets
127
-	}
128
-
129
-	countParam := "-c"
130
-	if runtime.GOOS == "windows" {
131
-		countParam = "-n" // Ping count parameter is -n on Windows
132
-	}
133
-	pingout, err := exec.Command("ping", contIP, countParam, strconv.Itoa(numPings)).CombinedOutput()
134
-	if err != nil && runtime.GOOS == "linux" {
135
-		// If it fails then try a work-around, but just for linux.
136
-		// If this fails too then go back to the old error for reporting.
137
-		//
138
-		// The ping will sometimes fail due to an apparmor issue where it
139
-		// denies access to the libc.so.6 shared library - running it
140
-		// via /lib64/ld-linux-x86-64.so.2 seems to work around it.
141
-		pingout2, err2 := exec.Command("/lib64/ld-linux-x86-64.so.2", "/bin/ping", contIP, "-c", strconv.Itoa(numPings)).CombinedOutput()
142
-		if err2 == nil {
143
-			pingout = pingout2
144
-			err = err2
145
-		}
146
-	}
147
-	assert.NilError(c, err)
148
-	pingouts := string(pingout[:])
149
-	nwStatsPost := getNetworkStats(c, id)
150
-	for _, v := range nwStatsPost {
151
-		postRxPackets += v.RxPackets
152
-		postTxPackets += v.TxPackets
153
-	}
154
-
155
-	// Verify the stats contain at least the expected number of packets
156
-	// On Linux, account for ARP.
157
-	expRxPkts := preRxPackets + uint64(numPings)
158
-	expTxPkts := preTxPackets + uint64(numPings)
159
-	if testEnv.DaemonInfo.OSType != "windows" {
160
-		expRxPkts++
161
-		expTxPkts++
162
-	}
163
-	assert.Assert(c, postTxPackets >= expTxPkts, "Reported less TxPackets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingouts)
164
-	assert.Assert(c, postRxPackets >= expRxPkts, "Reported less RxPackets than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingouts)
165
-}
166
-
167
-func getNetworkStats(t *testing.T, id string) map[string]container.NetworkStats {
168
-	_, body, err := request.Get(testutil.GetContext(t), "/containers/"+id+"/stats?stream=false")
169
-	assert.NilError(t, err)
170
-
171
-	var st container.StatsResponse
172
-	err = json.NewDecoder(body).Decode(&st)
173
-	assert.NilError(t, err)
174
-	_ = body.Close()
175
-
176
-	return st.Networks
177
-}
178
-
179 102
 func (s *DockerAPISuite) TestAPIStatsNoStreamConnectedContainers(c *testing.T) {
180 103
 	testRequires(c, DaemonIsLinux)
181 104
 
... ...
@@ -1,10 +1,15 @@
1 1
 package container
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"encoding/json"
5 6
 	"io"
7
+	"os/exec"
6 8
 	"reflect"
9
+	"runtime"
10
+	"strconv"
7 11
 	"testing"
12
+	"time"
8 13
 
9 14
 	cerrdefs "github.com/containerd/errdefs"
10 15
 	containertypes "github.com/moby/moby/api/types/container"
... ...
@@ -12,6 +17,7 @@ import (
12 12
 	"github.com/moby/moby/v2/integration/internal/container"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15
+	"gotest.tools/v3/poll"
15 16
 	"gotest.tools/v3/skip"
16 17
 )
17 18
 
... ...
@@ -93,3 +99,86 @@ func TestStatsContainerNotFound(t *testing.T) {
93 93
 		})
94 94
 	}
95 95
 }
96
+
97
+func TestStatsNetworkStats(t *testing.T) {
98
+	// FIXME(thaJeztah): Broken on Windows + containerd combination, see https://github.com/moby/moby/pull/41479
99
+	skip.If(t, testEnv.RuntimeIsWindowsContainerd(), "FIXME: Broken on Windows + containerd combination")
100
+	skip.If(t, testEnv.IsRootless() && testEnv.DaemonInfo.CgroupVersion == "1", "Rootless Mode does not support cgroups v1 stats")
101
+
102
+	ctx := setupTest(t)
103
+
104
+	apiClient := testEnv.APIClient()
105
+
106
+	cID := container.Run(ctx, t, apiClient)
107
+
108
+	net := "bridge"
109
+	if testEnv.DaemonInfo.OSType == "windows" {
110
+		net = "nat"
111
+	}
112
+
113
+	res, err := apiClient.ContainerInspect(ctx, cID, client.ContainerInspectOptions{})
114
+	assert.NilError(t, err)
115
+	containerIP := res.Container.NetworkSettings.Networks[net].IPAddress.String()
116
+
117
+	// Get the container networking stats before pinging the container
118
+	var preRxPackets, preTxPackets uint64
119
+	for _, v := range getNetworkStats(ctx, t, apiClient, cID) {
120
+		preRxPackets += v.RxPackets
121
+		preTxPackets += v.TxPackets
122
+	}
123
+
124
+	countParam := "-c"
125
+	if runtime.GOOS == "windows" {
126
+		countParam = "-n" // Ping count parameter is -n on Windows
127
+	}
128
+
129
+	numPings := 1
130
+	out, err := exec.Command("ping", containerIP, countParam, strconv.Itoa(numPings)).CombinedOutput()
131
+	if err != nil && runtime.GOOS == "linux" {
132
+		// If it fails then try a work-around, but just for linux.
133
+		// If this fails too then go back to the old error for reporting.
134
+		//
135
+		// The ping will sometimes fail due to an apparmor issue where it
136
+		// denies access to the libc.so.6 shared library - running it
137
+		// via /lib64/ld-linux-x86-64.so.2 seems to work around it.
138
+		out, err = exec.Command("/lib64/ld-linux-x86-64.so.2", "/bin/ping", containerIP, countParam, strconv.Itoa(numPings)).CombinedOutput()
139
+	}
140
+	pingOutput := string(out)
141
+	assert.NilError(t, err, pingOutput)
142
+
143
+	// Verify the stats contain at least the expected number of packets
144
+	expRxPkts := preRxPackets + uint64(numPings)
145
+	expTxPkts := preTxPackets + uint64(numPings)
146
+
147
+	// Poll for both PostTxPackets and PostRxPackets until they have the expected quantity
148
+	poll.WaitOn(t, func(l poll.LogT) poll.Result {
149
+		var postRxPackets, postTxPackets uint64
150
+		for _, v := range getNetworkStats(ctx, t, apiClient, cID) {
151
+			postTxPackets += v.TxPackets
152
+			postRxPackets += v.RxPackets
153
+		}
154
+
155
+		if postTxPackets < expTxPkts {
156
+			return poll.Continue("Reported less Tx packets than expected. Expected >= %d. Found %d. %s", expTxPkts, postTxPackets, pingOutput)
157
+		}
158
+
159
+		if postRxPackets < expRxPkts {
160
+			return poll.Continue("Reported less Rx packets than expected. Expected >= %d. Found %d. %s", expRxPkts, postRxPackets, pingOutput)
161
+		}
162
+
163
+		return poll.Success()
164
+	}, poll.WithDelay(100*time.Millisecond), poll.WithTimeout(2*time.Second))
165
+}
166
+
167
+func getNetworkStats(ctx context.Context, t *testing.T, apiClient client.APIClient, id string) map[string]containertypes.NetworkStats {
168
+	res, err := apiClient.ContainerStats(ctx, id, client.ContainerStatsOptions{Stream: false})
169
+	assert.NilError(t, err)
170
+
171
+	var st containertypes.StatsResponse
172
+	err = json.NewDecoder(res.Body).Decode(&st)
173
+
174
+	assert.NilError(t, err)
175
+	_ = res.Body.Close()
176
+
177
+	return st.Networks
178
+}