github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration-cli/docker_utils_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "path" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/Prakhar-Agarwal-byte/moby/api/types" 18 "github.com/Prakhar-Agarwal-byte/moby/client" 19 "github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli" 20 "github.com/Prakhar-Agarwal-byte/moby/integration-cli/daemon" 21 "github.com/Prakhar-Agarwal-byte/moby/testutil" 22 "gotest.tools/v3/assert" 23 "gotest.tools/v3/assert/cmp" 24 "gotest.tools/v3/icmd" 25 "gotest.tools/v3/poll" 26 ) 27 28 func deleteImages(images ...string) error { 29 args := []string{dockerBinary, "rmi", "-f"} 30 return icmd.RunCmd(icmd.Cmd{Command: append(args, images...)}).Error 31 } 32 33 // Deprecated: use cli.Docker or cli.DockerCmd 34 func dockerCmdWithError(args ...string) (string, int, error) { 35 result := cli.Docker(cli.Args(args...)) 36 if result.Error != nil { 37 return result.Combined(), result.ExitCode, result.Compare(icmd.Success) 38 } 39 return result.Combined(), result.ExitCode, result.Error 40 } 41 42 // Deprecated: use cli.Docker or cli.DockerCmd 43 func dockerCmdWithResult(args ...string) *icmd.Result { 44 return cli.Docker(cli.Args(args...)) 45 } 46 47 func findContainerIP(c *testing.T, id string, network string) string { 48 c.Helper() 49 out := cli.DockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", network), id).Stdout() 50 return strings.Trim(out, " \r\n'") 51 } 52 53 func getContainerCount(c *testing.T) int { 54 c.Helper() 55 const containers = "Containers:" 56 57 result := icmd.RunCommand(dockerBinary, "info") 58 result.Assert(c, icmd.Success) 59 60 lines := strings.Split(result.Combined(), "\n") 61 for _, line := range lines { 62 if strings.Contains(line, containers) { 63 output := strings.TrimSpace(line) 64 output = strings.TrimPrefix(output, containers) 65 output = strings.Trim(output, " ") 66 containerCount, err := strconv.Atoi(output) 67 assert.NilError(c, err) 68 return containerCount 69 } 70 } 71 return 0 72 } 73 74 func inspectFieldAndUnmarshall(c *testing.T, name, field string, output interface{}) { 75 c.Helper() 76 str := inspectFieldJSON(c, name, field) 77 err := json.Unmarshal([]byte(str), output) 78 assert.Assert(c, err == nil, "failed to unmarshal: %v", err) 79 } 80 81 // Deprecated: use cli.Docker 82 func inspectFilter(name, filter string) (string, error) { 83 format := fmt.Sprintf("{{%s}}", filter) 84 result := icmd.RunCommand(dockerBinary, "inspect", "-f", format, name) 85 if result.Error != nil || result.ExitCode != 0 { 86 return "", fmt.Errorf("failed to inspect %s: %s", name, result.Combined()) 87 } 88 return strings.TrimSpace(result.Combined()), nil 89 } 90 91 // Deprecated: use cli.Docker 92 func inspectFieldWithError(name, field string) (string, error) { 93 return inspectFilter(name, "."+field) 94 } 95 96 // Deprecated: use cli.Docker 97 func inspectField(c *testing.T, name, field string) string { 98 c.Helper() 99 out, err := inspectFilter(name, "."+field) 100 assert.NilError(c, err) 101 return out 102 } 103 104 // Deprecated: use cli.Docker 105 func inspectFieldJSON(c *testing.T, name, field string) string { 106 c.Helper() 107 out, err := inspectFilter(name, "json ."+field) 108 assert.NilError(c, err) 109 return out 110 } 111 112 // Deprecated: use cli.Docker 113 func inspectFieldMap(c *testing.T, name, path, field string) string { 114 c.Helper() 115 out, err := inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) 116 assert.NilError(c, err) 117 return out 118 } 119 120 // Deprecated: use cli.Docker 121 func inspectMountSourceField(name, destination string) (string, error) { 122 m, err := inspectMountPoint(name, destination) 123 if err != nil { 124 return "", err 125 } 126 return m.Source, nil 127 } 128 129 var errMountNotFound = errors.New("mount point not found") 130 131 // Deprecated: use cli.Docker 132 func inspectMountPoint(name, destination string) (types.MountPoint, error) { 133 out, err := inspectFilter(name, "json .Mounts") 134 if err != nil { 135 return types.MountPoint{}, err 136 } 137 138 var mp []types.MountPoint 139 if err := json.Unmarshal([]byte(out), &mp); err != nil { 140 return types.MountPoint{}, err 141 } 142 143 var m *types.MountPoint 144 for _, c := range mp { 145 if c.Destination == destination { 146 m = &c 147 break 148 } 149 } 150 151 if m == nil { 152 return types.MountPoint{}, errMountNotFound 153 } 154 155 return *m, nil 156 } 157 158 func getIDByName(c *testing.T, name string) string { 159 c.Helper() 160 id, err := inspectFieldWithError(name, "Id") 161 assert.NilError(c, err) 162 return id 163 } 164 165 // Deprecated: use cli.Docker 166 func buildImageSuccessfully(c *testing.T, name string, cmdOperators ...cli.CmdOperator) { 167 c.Helper() 168 buildImage(name, cmdOperators...).Assert(c, icmd.Success) 169 } 170 171 // Deprecated: use cli.Docker 172 func buildImage(name string, cmdOperators ...cli.CmdOperator) *icmd.Result { 173 return cli.Docker(cli.Args("build", "-t", name), cmdOperators...) 174 } 175 176 // Write `content` to the file at path `dst`, creating it if necessary, 177 // as well as any missing directories. 178 // The file is truncated if it already exists. 179 // Fail the test when error occurs. 180 func writeFile(dst, content string, c *testing.T) { 181 c.Helper() 182 // Create subdirectories if necessary 183 assert.Assert(c, os.MkdirAll(path.Dir(dst), 0o700) == nil) 184 f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o700) 185 assert.NilError(c, err) 186 defer f.Close() 187 // Write content (truncate if it exists) 188 _, err = io.Copy(f, strings.NewReader(content)) 189 assert.NilError(c, err) 190 } 191 192 // Return the contents of file at path `src`. 193 // Fail the test when error occurs. 194 func readFile(src string, c *testing.T) (content string) { 195 c.Helper() 196 data, err := os.ReadFile(src) 197 assert.NilError(c, err) 198 199 return string(data) 200 } 201 202 func containerStorageFile(containerID, basename string) string { 203 return filepath.Join(testEnv.PlatformDefaults.ContainerStoragePath, containerID, basename) 204 } 205 206 // docker commands that use this function must be run with the '-d' switch. 207 func runCommandAndReadContainerFile(c *testing.T, filename string, command string, args ...string) []byte { 208 c.Helper() 209 result := icmd.RunCommand(command, args...) 210 result.Assert(c, icmd.Success) 211 contID := strings.TrimSpace(result.Combined()) 212 cli.WaitRun(c, contID) 213 return readContainerFile(c, contID, filename) 214 } 215 216 func readContainerFile(c *testing.T, containerID, filename string) []byte { 217 c.Helper() 218 f, err := os.Open(containerStorageFile(containerID, filename)) 219 assert.NilError(c, err) 220 defer f.Close() 221 222 content, err := io.ReadAll(f) 223 assert.NilError(c, err) 224 return content 225 } 226 227 func readContainerFileWithExec(c *testing.T, containerID, filename string) []byte { 228 c.Helper() 229 result := icmd.RunCommand(dockerBinary, "exec", containerID, "cat", filename) 230 result.Assert(c, icmd.Success) 231 return []byte(result.Combined()) 232 } 233 234 // daemonTime provides the current time on the daemon host 235 func daemonTime(c *testing.T) time.Time { 236 c.Helper() 237 if testEnv.IsLocalDaemon() { 238 return time.Now() 239 } 240 apiClient, err := client.NewClientWithOpts(client.FromEnv) 241 assert.NilError(c, err) 242 defer apiClient.Close() 243 244 info, err := apiClient.Info(testutil.GetContext(c)) 245 assert.NilError(c, err) 246 247 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 248 assert.Assert(c, err == nil, "invalid time format in GET /info response") 249 return dt 250 } 251 252 // daemonUnixTime returns the current time on the daemon host with nanoseconds precision. 253 // It return the time formatted how the client sends timestamps to the server. 254 func daemonUnixTime(c *testing.T) string { 255 c.Helper() 256 return parseEventTime(daemonTime(c)) 257 } 258 259 func parseEventTime(t time.Time) string { 260 return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())) 261 } 262 263 // appendBaseEnv appends the minimum set of environment variables to exec the 264 // docker cli binary for testing with correct configuration to the given env 265 // list. 266 func appendBaseEnv(isTLS bool, env ...string) []string { 267 preserveList := []string{ 268 // preserve remote test host 269 "DOCKER_HOST", 270 271 // windows: requires preserving SystemRoot, otherwise dial tcp fails 272 // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." 273 "SystemRoot", 274 275 // testing help text requires the $PATH to dockerd is set 276 "PATH", 277 } 278 if isTLS { 279 preserveList = append(preserveList, "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH") 280 } 281 282 for _, key := range preserveList { 283 if val := os.Getenv(key); val != "" { 284 env = append(env, fmt.Sprintf("%s=%s", key, val)) 285 } 286 } 287 return env 288 } 289 290 func createTmpFile(c *testing.T, content string) string { 291 c.Helper() 292 f, err := os.CreateTemp("", "testfile") 293 assert.NilError(c, err) 294 295 filename := f.Name() 296 297 err = os.WriteFile(filename, []byte(content), 0o644) 298 assert.NilError(c, err) 299 300 return filename 301 } 302 303 // waitInspect will wait for the specified container to have the specified string 304 // in the inspect output. It will wait until the specified timeout (in seconds) 305 // is reached. 306 // Deprecated: use cli.WaitFor 307 func waitInspect(name, expr, expected string, timeout time.Duration) error { 308 return daemon.WaitInspectWithArgs(dockerBinary, name, expr, expected, timeout) 309 } 310 311 func getInspectBody(c *testing.T, version, id string) []byte { 312 c.Helper() 313 apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(version)) 314 assert.NilError(c, err) 315 defer apiClient.Close() 316 _, body, err := apiClient.ContainerInspectWithRaw(testutil.GetContext(c), id, false) 317 assert.NilError(c, err) 318 return body 319 } 320 321 // Run a long running idle task in a background container using the 322 // system-specific default image and command. 323 func runSleepingContainer(c *testing.T, extraArgs ...string) string { 324 c.Helper() 325 return runSleepingContainerInImage(c, "busybox", extraArgs...) 326 } 327 328 // Run a long running idle task in a background container using the specified 329 // image and the system-specific command. 330 func runSleepingContainerInImage(c *testing.T, image string, extraArgs ...string) string { 331 c.Helper() 332 args := []string{"run", "-d"} 333 args = append(args, extraArgs...) 334 args = append(args, image) 335 args = append(args, sleepCommandForDaemonPlatform()...) 336 return strings.TrimSpace(cli.DockerCmd(c, args...).Combined()) 337 } 338 339 // minimalBaseImage returns the name of the minimal base image for the current 340 // daemon platform. 341 func minimalBaseImage() string { 342 return testEnv.PlatformDefaults.BaseImage 343 } 344 345 func getGoroutineNumber(ctx context.Context, apiClient client.APIClient) (int, error) { 346 info, err := apiClient.Info(ctx) 347 if err != nil { 348 return 0, err 349 } 350 return info.NGoroutines, nil 351 } 352 353 func waitForStableGourtineCount(ctx context.Context, t poll.TestingT, apiClient client.APIClient) int { 354 var out int 355 poll.WaitOn(t, stableGoroutineCount(ctx, apiClient, &out), poll.WithTimeout(30*time.Second)) 356 return out 357 } 358 359 func stableGoroutineCount(ctx context.Context, apiClient client.APIClient, count *int) poll.Check { 360 var ( 361 numStable int 362 nRoutines int 363 ) 364 365 return func(t poll.LogT) poll.Result { 366 n, err := getGoroutineNumber(ctx, apiClient) 367 if err != nil { 368 return poll.Error(err) 369 } 370 371 last := nRoutines 372 373 if nRoutines == n { 374 numStable++ 375 } else { 376 numStable = 0 377 nRoutines = n 378 } 379 380 if numStable > 3 { 381 *count = n 382 return poll.Success() 383 } 384 return poll.Continue("goroutine count is not stable: last %d, current %d, stable iters: %d", last, n, numStable) 385 } 386 } 387 388 func checkGoroutineCount(ctx context.Context, apiClient client.APIClient, expected int) poll.Check { 389 first := true 390 return func(t poll.LogT) poll.Result { 391 n, err := getGoroutineNumber(ctx, apiClient) 392 if err != nil { 393 return poll.Error(err) 394 } 395 if n > expected { 396 if first { 397 t.Log("Waiting for goroutines to stabilize") 398 first = false 399 } 400 return poll.Continue("exepcted %d goroutines, got %d", expected, n) 401 } 402 return poll.Success() 403 } 404 } 405 406 func waitForGoroutines(ctx context.Context, t poll.TestingT, apiClient client.APIClient, expected int) { 407 poll.WaitOn(t, checkGoroutineCount(ctx, apiClient, expected), poll.WithDelay(500*time.Millisecond), poll.WithTimeout(30*time.Second)) 408 } 409 410 // getErrorMessage returns the error message from an error API response 411 func getErrorMessage(c *testing.T, body []byte) string { 412 c.Helper() 413 var resp types.ErrorResponse 414 assert.Assert(c, json.Unmarshal(body, &resp) == nil) 415 return strings.TrimSpace(resp.Message) 416 } 417 418 type ( 419 checkF func(*testing.T) (interface{}, string) 420 reducer func(...interface{}) interface{} 421 ) 422 423 func pollCheck(t *testing.T, f checkF, compare func(x interface{}) assert.BoolOrComparison) poll.Check { 424 return func(poll.LogT) poll.Result { 425 t.Helper() 426 v, comment := f(t) 427 r := compare(v) 428 switch r := r.(type) { 429 case bool: 430 if r { 431 return poll.Success() 432 } 433 case cmp.Comparison: 434 if r().Success() { 435 return poll.Success() 436 } 437 default: 438 panic(fmt.Errorf("pollCheck: type %T not implemented", r)) 439 } 440 return poll.Continue(comment) 441 } 442 } 443 444 func reducedCheck(r reducer, funcs ...checkF) checkF { 445 return func(c *testing.T) (interface{}, string) { 446 c.Helper() 447 var values []interface{} 448 var comments []string 449 for _, f := range funcs { 450 v, comment := f(c) 451 values = append(values, v) 452 if len(comment) > 0 { 453 comments = append(comments, comment) 454 } 455 } 456 return r(values...), fmt.Sprintf("%v", strings.Join(comments, ", ")) 457 } 458 } 459 460 func sumAsIntegers(vals ...interface{}) interface{} { 461 var s int 462 for _, v := range vals { 463 s += v.(int) 464 } 465 return s 466 }