diff --git a/executor/mock/jobrunner.go b/executor/mock/jobrunner.go index de9121062..14ad2f0f0 100644 --- a/executor/mock/jobrunner.go +++ b/executor/mock/jobrunner.go @@ -57,6 +57,8 @@ type JobInput struct { CPU *int64 // StopTimeoutSeconds is the duration we wait after SIGTERM for the container to exit KillWaitSeconds uint32 + // Tty attaches a tty to the container via a passthrough attribute + Tty bool } // JobRunResponse returned from RunJob @@ -201,14 +203,16 @@ func (jobRunner *JobRunner) StopExecutorAsync() { } // StartJob starts a job on an existing JobRunner and returns once the job is started -func (jobRunner *JobRunner) StartJob(jobInput *JobInput) *JobRunResponse { +func (jobRunner *JobRunner) StartJob(jobInput *JobInput) *JobRunResponse { // nolint: gocyclo // Define some stock job to run var jobID string + if jobInput.JobID == "" { jobID = fmt.Sprintf("Titus-%v%v", r.Intn(1000), time.Now().Second()) } else { jobID = jobInput.JobID } + taskID := fmt.Sprintf("Titus-%v%v-Worker-0-2", r.Intn(1000), time.Now().Second()) env := map[string]string{ "TITUS_TASK_ID": taskID, @@ -249,6 +253,9 @@ func (jobRunner *JobRunner) StartJob(jobInput *JobInput) *JobRunResponse { if jobInput.Batch == "idle" { ci.PassthroughAttributes["titusParameter.agent.batchPriority"] = "idle" } + if jobInput.Tty { + ci.PassthroughAttributes["titusParameter.agent.ttyEnabled"] = "true" + } if jobInput.KillWaitSeconds > 0 { ci.KillWaitSeconds = protobuf.Uint32(jobInput.KillWaitSeconds) diff --git a/executor/mock/standalone/standalone_test.go b/executor/mock/standalone/standalone_test.go index 41a19d2f8..952b9d851 100644 --- a/executor/mock/standalone/standalone_test.go +++ b/executor/mock/standalone/standalone_test.go @@ -122,6 +122,8 @@ func TestStandalone(t *testing.T) { testNoCPUBursting, testCPUBursting, testTwoCPUs, + testTty, + testTtyNegative, } for _, fun := range testFunctions { fullName := runtime.FuncForPC(reflect.ValueOf(fun).Pointer()).Name() @@ -847,3 +849,29 @@ func testTwoCPUs(t *testing.T, jobID string) { t.Fail() } } + +func testTty(t *testing.T, jobID string) { + ji := &mock.JobInput{ + ImageName: ubuntu.name, + Version: ubuntu.tag, + EntrypointOld: `/usr/bin/tty`, + JobID: jobID, + Tty: true, + } + if !mock.RunJobExpectingSuccess(ji) { + t.Fail() + } +} + +func testTtyNegative(t *testing.T, jobID string) { + ji := &mock.JobInput{ + ImageName: ubuntu.name, + Version: ubuntu.tag, + EntrypointOld: `/usr/bin/tty`, + JobID: jobID, + // Tty not specified + } + if !mock.RunJobExpectingFailure(ji) { + t.Fail() + } +} diff --git a/executor/runtime/docker/docker.go b/executor/runtime/docker/docker.go index c52a1ee58..e0bd644dd 100644 --- a/executor/runtime/docker/docker.go +++ b/executor/runtime/docker/docker.go @@ -407,6 +407,11 @@ func (r *DockerRuntime) dockerConfig(c *runtimeTypes.Container, binds []string, return nil, nil, err } + tty, err := c.GetTty() + if err != nil { + return nil, nil, err + } + containerCfg := &container.Config{ Image: c.QualifiedImageName(), Entrypoint: entrypoint, @@ -414,6 +419,7 @@ func (r *DockerRuntime) dockerConfig(c *runtimeTypes.Container, binds []string, Labels: c.Labels, Volumes: map[string]struct{}{}, Hostname: hostname, + Tty: tty, } useInit := true diff --git a/executor/runtime/types/types.go b/executor/runtime/types/types.go index 5be6188bd..79c3c3fce 100644 --- a/executor/runtime/types/types.go +++ b/executor/runtime/types/types.go @@ -27,6 +27,7 @@ const ( // FuseEnabledParam is a container atttribute set to enable FUSE FuseEnabledParam = "titusParameter.agent.fuseEnabled" assignIPv6AddressParam = "titusParameter.agent.assignIPv6Address" + ttyEnabledParam = "titusParameter.agent.ttyEnabled" ) // ErrMissingIAMRole indicates that the Titus job was submitted without an IAM role @@ -279,6 +280,20 @@ func (c *Container) AssignIPv6Address() (bool, error) { return val, nil } +// GetTty should the container be assigned a tty? +func (c *Container) GetTty() (bool, error) { + ttyEnabledStr, ok := c.TitusInfo.GetPassthroughAttributes()[ttyEnabledParam] + if !ok { + return false, nil + } + val, err := strconv.ParseBool(ttyEnabledStr) + if err != nil { + return false, err + } + + return val, nil +} + // Resources specify constraints to be applied to a Container type Resources struct { Mem int64 // in MiB diff --git a/executor/runtime/types/types_test.go b/executor/runtime/types/types_test.go index 606a516f5..9e3c9babf 100644 --- a/executor/runtime/types/types_test.go +++ b/executor/runtime/types/types_test.go @@ -154,3 +154,14 @@ func TestIPv6AddressAssignment(t *testing.T) { assert.NoError(t, err) assert.True(t, assignIPv6Address) } + +func TestTtyEnabled(t *testing.T) { + c := Container{ + TitusInfo: &titus.ContainerInfo{ + PassthroughAttributes: map[string]string{ttyEnabledParam: "true"}, + }, + } + tty, err := c.GetTty() + assert.NoError(t, err) + assert.True(t, tty) +}