UI: add exec handling for dead jobs/task states (#7637)
This closes #7456. It hides the terminal when the job is dead and displays an error when trying to open an exec session for a task that isn’t running. There’s a skipped test for the latter behaviour that I’ll have to come back for.
This commit is contained in:
parent
fc7de8b153
commit
f10906e006
|
@ -87,45 +87,51 @@ export default Controller.extend({
|
|||
taskGroupName,
|
||||
});
|
||||
|
||||
this.terminal.write(ANSI_UI_GRAY_400);
|
||||
this.terminal.writeln('');
|
||||
|
||||
if (!allocationShortId) {
|
||||
this.terminal.writeln(
|
||||
'Multiple instances of this task are running. The allocation below was selected by random draw.'
|
||||
);
|
||||
if (this.taskState) {
|
||||
this.terminal.write(ANSI_UI_GRAY_400);
|
||||
this.terminal.writeln('');
|
||||
|
||||
if (!allocationShortId) {
|
||||
this.terminal.writeln(
|
||||
'Multiple instances of this task are running. The allocation below was selected by random draw.'
|
||||
);
|
||||
this.terminal.writeln('');
|
||||
}
|
||||
|
||||
this.terminal.writeln('Customize your command, then hit ‘return’ to run.');
|
||||
this.terminal.writeln('');
|
||||
this.terminal.write(
|
||||
`$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${
|
||||
this.taskState.allocation.shortId
|
||||
} `
|
||||
);
|
||||
|
||||
this.terminal.write(ANSI_WHITE);
|
||||
|
||||
this.terminal.write(this.command);
|
||||
|
||||
if (this.commandEditorAdapter) {
|
||||
this.commandEditorAdapter.destroy();
|
||||
}
|
||||
|
||||
this.commandEditorAdapter = new ExecCommandEditorXtermAdapter(
|
||||
this.terminal,
|
||||
this.openAndConnectSocket.bind(this),
|
||||
this.command
|
||||
);
|
||||
}
|
||||
|
||||
this.terminal.writeln('Customize your command, then hit ‘return’ to run.');
|
||||
this.terminal.writeln('');
|
||||
this.terminal.write(
|
||||
`$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${
|
||||
this.taskState.allocation.shortId
|
||||
} `
|
||||
);
|
||||
|
||||
this.terminal.write(ANSI_WHITE);
|
||||
|
||||
this.terminal.write(this.command);
|
||||
|
||||
if (this.commandEditorAdapter) {
|
||||
this.commandEditorAdapter.destroy();
|
||||
}
|
||||
|
||||
this.commandEditorAdapter = new ExecCommandEditorXtermAdapter(
|
||||
this.terminal,
|
||||
this.openAndConnectSocket.bind(this),
|
||||
this.command
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
openAndConnectSocket(command) {
|
||||
this.set('socketOpen', true);
|
||||
this.set('command', command);
|
||||
this.socket = this.sockets.getTaskStateSocket(this.taskState, command);
|
||||
if (this.taskState) {
|
||||
this.set('socketOpen', true);
|
||||
this.set('command', command);
|
||||
this.socket = this.sockets.getTaskStateSocket(this.taskState, command);
|
||||
|
||||
new ExecSocketXtermAdapter(this.terminal, this.socket, this.token.secret);
|
||||
new ExecSocketXtermAdapter(this.terminal, this.socket, this.token.secret);
|
||||
} else {
|
||||
this.terminal.writeln(`Failed to open a socket because task ${this.taskName} is not active.`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
padding: 16px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
color: white;
|
||||
|
||||
.terminal {
|
||||
height: 100%;
|
||||
|
|
|
@ -29,20 +29,30 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="tree-and-terminal">
|
||||
<div class="task-group-tree">
|
||||
<h4 class="title is-6">Tasks</h4>
|
||||
<ul>
|
||||
{{#each sortedTaskGroups as |taskGroup|}}
|
||||
<li data-test-task-group>
|
||||
{{exec/task-group-parent
|
||||
taskGroup=taskGroup
|
||||
openInNewWindow=socketOpen
|
||||
activeTaskName=taskName
|
||||
activeTaskGroupName=taskGroupName}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if (eq model.status "dead")}}
|
||||
<div class="tree-and-terminal" data-test-exec-job-dead>
|
||||
<div class="task-group-tree">
|
||||
</div>
|
||||
<div class="terminal-container" data-test-exec-job-dead-message>
|
||||
Job <code>{{model.name}}</code> is dead and cannot host an exec session.
|
||||
</div>
|
||||
</div>
|
||||
{{exec-terminal terminal=terminal}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="tree-and-terminal">
|
||||
<div class="task-group-tree">
|
||||
<h4 class="title is-6">Tasks</h4>
|
||||
<ul>
|
||||
{{#each sortedTaskGroups as |taskGroup|}}
|
||||
<li data-test-task-group>
|
||||
{{exec/task-group-parent
|
||||
taskGroup=taskGroup
|
||||
openInNewWindow=socketOpen
|
||||
activeTaskName=taskName
|
||||
activeTaskGroupName=taskGroupName}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{exec-terminal terminal=terminal}}
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,4 +1,4 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { module, skip, test } from 'qunit';
|
||||
import { currentURL, settled } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
|
@ -21,6 +21,7 @@ module('Acceptance | exec', function(hooks) {
|
|||
groupsCount: 2,
|
||||
groupTaskCount: 5,
|
||||
createAllocations: false,
|
||||
status: 'running',
|
||||
});
|
||||
|
||||
this.job.task_group_ids.forEach(taskGroupId => {
|
||||
|
@ -39,7 +40,11 @@ module('Acceptance | exec', function(hooks) {
|
|||
server.create('region', { id: 'global' });
|
||||
server.create('region', { id: 'region-2' });
|
||||
|
||||
this.job = server.create('job', { createAllocations: false, namespaceId: namespace.id });
|
||||
this.job = server.create('job', {
|
||||
createAllocations: false,
|
||||
namespaceId: namespace.id,
|
||||
status: 'running',
|
||||
});
|
||||
|
||||
await Exec.visitJob({ job: this.job.id, namespace: namespace.id, region: 'region-2' });
|
||||
|
||||
|
@ -48,6 +53,8 @@ module('Acceptance | exec', function(hooks) {
|
|||
assert.equal(Exec.header.region.text, this.job.region);
|
||||
assert.equal(Exec.header.namespace.text, this.job.namespace);
|
||||
assert.equal(Exec.header.job, this.job.name);
|
||||
|
||||
assert.notOk(Exec.jobDead.isPresent);
|
||||
});
|
||||
|
||||
test('/exec/:job should not show region and namespace when there are none', async function(assert) {
|
||||
|
@ -167,6 +174,42 @@ module('Acceptance | exec', function(hooks) {
|
|||
assert.equal(Exec.taskGroups[0].tasks[1].name, changingTaskStateName);
|
||||
});
|
||||
|
||||
test('a dead job has an inert window', async function(assert) {
|
||||
this.job.status = 'dead';
|
||||
this.job.save();
|
||||
|
||||
let taskGroup = this.job.task_groups.models.sortBy('name')[0];
|
||||
let task = taskGroup.tasks.models.sortBy('name')[0];
|
||||
|
||||
this.server.db.taskStates.update({ finishedAt: new Date() });
|
||||
|
||||
await Exec.visitTask({
|
||||
job: this.job.id,
|
||||
task_group: taskGroup.name,
|
||||
task_name: task.name,
|
||||
});
|
||||
|
||||
assert.ok(Exec.jobDead.isPresent);
|
||||
assert.equal(
|
||||
Exec.jobDead.message,
|
||||
`Job ${this.job.name} is dead and cannot host an exec session.`
|
||||
);
|
||||
});
|
||||
|
||||
test('when a job dies the exec window becomes inert', async function(assert) {
|
||||
await Exec.visitJob({ job: this.job.id });
|
||||
|
||||
// Approximate live-polling job death
|
||||
this.owner
|
||||
.lookup('service:store')
|
||||
.peekAll('job')
|
||||
.forEach(job => job.set('status', 'dead'));
|
||||
|
||||
await settled();
|
||||
|
||||
assert.ok(Exec.jobDead.isPresent);
|
||||
});
|
||||
|
||||
test('visiting a path with a task group should open the group by default', async function(assert) {
|
||||
let taskGroup = this.job.task_groups.models.sortBy('name')[0];
|
||||
await Exec.visitTaskGroup({ job: this.job.id, task_group: taskGroup.name });
|
||||
|
@ -485,6 +528,34 @@ module('Acceptance | exec', function(hooks) {
|
|||
`$ nomad alloc exec -i -t -task ${task.name} ${allocation.id.split('-')[0]} /bin/sh`
|
||||
);
|
||||
});
|
||||
|
||||
skip('when a task state finishes submitting a command displays an error', async function(assert) {
|
||||
let taskGroup = this.job.task_groups.models.sortBy('name')[0];
|
||||
let task = taskGroup.tasks.models.sortBy('name')[0];
|
||||
|
||||
await Exec.visitTask({
|
||||
job: this.job.id,
|
||||
task_group: taskGroup.name,
|
||||
task_name: task.name,
|
||||
});
|
||||
|
||||
// Approximate allocation failure via polling
|
||||
this.owner
|
||||
.lookup('service:store')
|
||||
.peekAll('allocation')
|
||||
.forEach(allocation => allocation.set('clientStatus', 'failed'));
|
||||
|
||||
await Exec.terminal.pressEnter();
|
||||
await settled();
|
||||
|
||||
assert.equal(
|
||||
window.execTerminal.buffer
|
||||
.getLine(7)
|
||||
.translateToString()
|
||||
.trim(),
|
||||
`Failed to open a socket because task ${task.name} is not active.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
class MockSocket {
|
||||
|
|
|
@ -42,4 +42,9 @@ export default create({
|
|||
scope: '.xterm-helper-textarea',
|
||||
pressEnter: triggerable('keydown', '', { eventProperties: { keyCode: 13 } }),
|
||||
},
|
||||
|
||||
jobDead: {
|
||||
scope: '[data-test-exec-job-dead]',
|
||||
message: text('[data-test-exec-job-dead-message]'),
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue