Skip to content

Commit

Permalink
Add exec heartbeat keepalive (#8759)
Browse files Browse the repository at this point in the history
This closes #8727, thanks to @jfcantu for the suggestion.
The CLI implementation of exec already has a 10-second
heartbeat so this mirrors that:
https://github.com/hashicorp/nomad/blob/v0.12.3/api/allocations.go#L161-L173
  • Loading branch information
backspace committed Aug 28, 2020
1 parent 147a61c commit 0c9b2e4
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ IMPROVEMENTS:
* api: Added node purge SDK functionality. [[GH-8142](https://github.com/hashicorp/nomad/issues/8142)]
* csi: Improved the accuracy of plugin `Expected` allocation counts. [[GH-8699](https://github.com/hashicorp/nomad/pull/8699)]
* driver/docker: Allow configurable image pull context timeout setting. [[GH-5718](https://github.com/hashicorp/nomad/issues/5718)]
* ui: Added exec keepalive heartbeat. [[GH-8759](https://github.com/hashicorp/nomad/pull/8759)]

BUG FIXES:

Expand Down
14 changes: 14 additions & 0 deletions ui/app/utils/classes/exec-socket-xterm-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const ANSI_UI_GRAY_400 = '\x1b[38;2;142;150;163m';
import base64js from 'base64-js';
import { TextDecoderLite, TextEncoderLite } from 'text-encoder-lite';

export const HEARTBEAT_INTERVAL = 10000; // ten seconds

export default class ExecSocketXtermAdapter {
constructor(terminal, socket, token) {
this.terminal = terminal;
Expand All @@ -12,6 +14,7 @@ export default class ExecSocketXtermAdapter {
socket.onopen = () => {
this.sendWsHandshake();
this.sendTtySize();
this.startHeartbeat();

terminal.onData(data => {
this.handleData(data);
Expand All @@ -28,6 +31,7 @@ export default class ExecSocketXtermAdapter {
};

socket.onclose = () => {
this.stopHeartbeat();
this.terminal.writeln('');
this.terminal.write(ANSI_UI_GRAY_400);
this.terminal.writeln('The connection has closed.');
Expand All @@ -49,6 +53,16 @@ export default class ExecSocketXtermAdapter {
this.socket.send(JSON.stringify({ version: 1, auth_token: this.token || '' }));
}

startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.socket.send(JSON.stringify({}));
}, HEARTBEAT_INTERVAL);
}

stopHeartbeat() {
clearInterval(this.heartbeatTimer);
}

handleData(data) {
this.socket.send(JSON.stringify({ stdin: { data: encodeString(data) } }));
}
Expand Down
42 changes: 42 additions & 0 deletions ui/tests/integration/util/exec-socket-xterm-adapter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { module, test } from 'qunit';
import { render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { Terminal } from 'xterm';
import { HEARTBEAT_INTERVAL } from 'nomad-ui/utils/classes/exec-socket-xterm-adapter';
import sinon from 'sinon';

module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
setupRenderingTest(hooks);
Expand All @@ -26,6 +28,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
if (firstMessage) {
firstMessage = false;
assert.deepEqual(message, JSON.stringify({ version: 1, auth_token: 'mysecrettoken' }));
mockSocket.onclose();
done();
}
},
Expand Down Expand Up @@ -56,6 +59,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
if (firstMessage) {
firstMessage = false;
assert.deepEqual(message, JSON.stringify({ version: 1, auth_token: '' }));
mockSocket.onclose();
done();
}
},
Expand All @@ -68,6 +72,40 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
await settled();
});

test('a heartbeat is sent periodically', async function(assert) {
let done = assert.async();

const clock = sinon.useFakeTimers({
now: new Date(),
shouldAdvanceTime: true,
});

let terminal = new Terminal();
this.set('terminal', terminal);

await render(hbs`
<ExecTerminal @terminal={{terminal}} />
`);

await settled();

let mockSocket = new Object({
send(message) {
if (!message.includes('version') && !message.includes('tty_size')) {
assert.deepEqual(message, JSON.stringify({}));
clock.restore();
mockSocket.onclose();
done();
}
},
});

new ExecSocketXtermAdapter(terminal, mockSocket, null);
mockSocket.onopen();
await settled();
clock.tick(HEARTBEAT_INTERVAL);
});

test('resizing the window passes a resize message through the socket', async function(assert) {
let done = assert.async();

Expand All @@ -86,6 +124,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
message,
JSON.stringify({ tty_size: { width: terminal.cols, height: terminal.rows } })
);
mockSocket.onclose();
done();
},
});
Expand Down Expand Up @@ -120,6 +159,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
});

await settled();
mockSocket.onclose();
});

test('stderr frames are ignored', async function(assert) {
Expand Down Expand Up @@ -155,5 +195,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) {
.trim(),
'sh-3.2 🥳$'
);

mockSocket.onclose();
});
});

0 comments on commit 0c9b2e4

Please sign in to comment.