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,6 +87,7 @@ export default Controller.extend({
|
||||||
taskGroupName,
|
taskGroupName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.taskState) {
|
||||||
this.terminal.write(ANSI_UI_GRAY_400);
|
this.terminal.write(ANSI_UI_GRAY_400);
|
||||||
this.terminal.writeln('');
|
this.terminal.writeln('');
|
||||||
|
|
||||||
|
@ -118,14 +119,19 @@ export default Controller.extend({
|
||||||
this.openAndConnectSocket.bind(this),
|
this.openAndConnectSocket.bind(this),
|
||||||
this.command
|
this.command
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
openAndConnectSocket(command) {
|
openAndConnectSocket(command) {
|
||||||
|
if (this.taskState) {
|
||||||
this.set('socketOpen', true);
|
this.set('socketOpen', true);
|
||||||
this.set('command', command);
|
this.set('command', command);
|
||||||
this.socket = this.sockets.getTaskStateSocket(this.taskState, 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;
|
padding: 16px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
color: white;
|
||||||
|
|
||||||
.terminal {
|
.terminal {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -29,7 +29,16 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="tree-and-terminal">
|
{{#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>
|
||||||
|
{{else}}
|
||||||
|
<div class="tree-and-terminal">
|
||||||
<div class="task-group-tree">
|
<div class="task-group-tree">
|
||||||
<h4 class="title is-6">Tasks</h4>
|
<h4 class="title is-6">Tasks</h4>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -45,4 +54,5 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{exec-terminal terminal=terminal}}
|
{{exec-terminal terminal=terminal}}
|
||||||
</div>
|
</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 { currentURL, settled } from '@ember/test-helpers';
|
||||||
import { setupApplicationTest } from 'ember-qunit';
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
@ -21,6 +21,7 @@ module('Acceptance | exec', function(hooks) {
|
||||||
groupsCount: 2,
|
groupsCount: 2,
|
||||||
groupTaskCount: 5,
|
groupTaskCount: 5,
|
||||||
createAllocations: false,
|
createAllocations: false,
|
||||||
|
status: 'running',
|
||||||
});
|
});
|
||||||
|
|
||||||
this.job.task_group_ids.forEach(taskGroupId => {
|
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: 'global' });
|
||||||
server.create('region', { id: 'region-2' });
|
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' });
|
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.region.text, this.job.region);
|
||||||
assert.equal(Exec.header.namespace.text, this.job.namespace);
|
assert.equal(Exec.header.namespace.text, this.job.namespace);
|
||||||
assert.equal(Exec.header.job, this.job.name);
|
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) {
|
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);
|
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) {
|
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];
|
let taskGroup = this.job.task_groups.models.sortBy('name')[0];
|
||||||
await Exec.visitTaskGroup({ job: this.job.id, task_group: taskGroup.name });
|
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`
|
`$ 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 {
|
class MockSocket {
|
||||||
|
|
|
@ -42,4 +42,9 @@ export default create({
|
||||||
scope: '.xterm-helper-textarea',
|
scope: '.xterm-helper-textarea',
|
||||||
pressEnter: triggerable('keydown', '', { eventProperties: { keyCode: 13 } }),
|
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