From 1d489450fec7ffd868982cd10dfea1446921dc80 Mon Sep 17 00:00:00 2001 From: hc-github-team-nomad-core <82989552+hc-github-team-nomad-core@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:59:44 -0400 Subject: [PATCH] backport of commit b18851617fdeb420f07c6b566514a30bc10fe051 (#24243) Co-authored-by: Seth Hoenig --- .changelog/24202.txt | 3 + drivers/docker/driver.go | 1 + e2e/allocexec/doc.go | 5 ++ e2e/allocexec/docker_exec_test.go | 94 +++++++++++++++++++++++++++++++ e2e/allocexec/input/sleepytar.hcl | 45 +++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 .changelog/24202.txt create mode 100644 e2e/allocexec/doc.go create mode 100644 e2e/allocexec/docker_exec_test.go create mode 100644 e2e/allocexec/input/sleepytar.hcl diff --git a/.changelog/24202.txt b/.changelog/24202.txt new file mode 100644 index 00000000000..12d709c05ea --- /dev/null +++ b/.changelog/24202.txt @@ -0,0 +1,3 @@ +```release-note:bug +docker: Fixed a bug where alloc exec with stdin would hang +``` diff --git a/drivers/docker/driver.go b/drivers/docker/driver.go index 63a5762826e..c2e64567de9 100644 --- a/drivers/docker/driver.go +++ b/drivers/docker/driver.go @@ -1848,6 +1848,7 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri go func() { _, _ = io.Copy(resp.Conn, opts.Stdin) + _ = resp.CloseWrite() }() exitCode := 999 diff --git a/e2e/allocexec/doc.go b/e2e/allocexec/doc.go new file mode 100644 index 00000000000..2cd3b5cd875 --- /dev/null +++ b/e2e/allocexec/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package allocexec contains tests around the alloc exec functionality. +package allocexec diff --git a/e2e/allocexec/docker_exec_test.go b/e2e/allocexec/docker_exec_test.go new file mode 100644 index 00000000000..2956d700d81 --- /dev/null +++ b/e2e/allocexec/docker_exec_test.go @@ -0,0 +1,94 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package allocexec + +import ( + "archive/tar" + "bytes" + "context" + "strings" + "testing" + "time" + + nomadapi "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/e2e/v3/cluster3" + "github.com/hashicorp/nomad/e2e/v3/jobs3" + "github.com/shoenig/test/must" +) + +func TestDockerAllocExec(t *testing.T) { + cluster3.Establish(t, + cluster3.Leader(), + cluster3.LinuxClients(1), + ) + + t.Run("testDockerExecStdin", testDockerExecStdin) +} + +func testDockerExecStdin(t *testing.T) { + _, cleanup := jobs3.Submit(t, "./input/sleepytar.hcl") + t.Cleanup(cleanup) + + client, err := nomadapi.NewClient(nomadapi.DefaultConfig()) + must.NoError(t, err) + + allocations, _, err := client.Allocations().List(nil) + must.NoError(t, err) + must.SliceLen(t, 1, allocations) + + // Use the first allocation for the example + allocationID := allocations[0].ID + allocation, _, err := client.Allocations().Info(allocationID, nil) + must.NoError(t, err) + + // Command to execute + command := []string{"tar", "--extract", "--verbose", "--file=/dev/stdin"} + + // Create a buffer to hold the tar archive + var tarBuffer bytes.Buffer + tarWriter := tar.NewWriter(&tarBuffer) + + // Create a tar header + fileContentLength := 8100 + header := &tar.Header{ + Name: "filename.txt", + Mode: 0600, + Size: int64(len(strings.Repeat("a", fileContentLength))), + } + + // Write the header to the tar archive + must.NoError(t, tarWriter.WriteHeader(header)) + + // Write the file content to the tar archive + _, err = tarWriter.Write([]byte(strings.Repeat("a", fileContentLength))) + must.NoError(t, err) + + // Close the tar writer + must.Close(t, tarWriter) + + output := new(bytes.Buffer) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // execute the tar command inside the container + exitCode, err := client.Allocations().Exec( + ctx, + allocation, + "task", + false, + command, + &tarBuffer, + output, + output, + nil, + nil, + ) + must.NoError(t, err) + must.Zero(t, exitCode) + + // check the output of tar + s := output.String() + must.Eq(t, "filename.txt\n", s) +} diff --git a/e2e/allocexec/input/sleepytar.hcl b/e2e/allocexec/input/sleepytar.hcl new file mode 100644 index 00000000000..13123806447 --- /dev/null +++ b/e2e/allocexec/input/sleepytar.hcl @@ -0,0 +1,45 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +# This "sleepytar" job just sleeps, but is used as a target for a nomad alloc +# exec API invocation to run a tar job that reads its data from stdin. + +job "sleepytar" { + constraint { + attribute = "${attr.kernel.name}" + value = "linux" + } + + group "group" { + update { + min_healthy_time = "3s" + } + + reschedule { + unlimited = false + attempts = 0 + } + + restart { + attempts = 0 + mode = "fail" + } + + task "task" { + driver = "docker" + + config { + image = "bash:latest" + command = "sleep" + args = ["infinity"] + network_mode = "none" + } + + resources { + cores = 1 + memory = 128 + } + } + } +} +