Skip to content

Commit

Permalink
Fallback to using the nomad server for log streaming
Browse files Browse the repository at this point in the history
Only when the client isn't accessible
DingoEatingFuzz committed Feb 26, 2018

Unverified

This user has not yet uploaded their public signing key.
1 parent 39f9914 commit 5346f65
Showing 5 changed files with 93 additions and 33 deletions.
27 changes: 22 additions & 5 deletions ui/app/components/task-log.js
Original file line number Diff line number Diff line change
@@ -2,9 +2,11 @@ import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { computed } from '@ember/object';
import { run } from '@ember/runloop';
import RSVP from 'rsvp';
import { task } from 'ember-concurrency';
import { logger } from 'nomad-ui/utils/classes/log';
import WindowResizable from 'nomad-ui/mixins/window-resizable';
import timeout from 'nomad-ui/utils/timeout';

export default Component.extend(WindowResizable, {
token: service(),
@@ -14,6 +16,8 @@ export default Component.extend(WindowResizable, {
allocation: null,
task: null,

useServer: false,

didReceiveAttrs() {
if (this.get('allocation') && this.get('task')) {
this.send('toggleStream');
@@ -37,11 +41,12 @@ export default Component.extend(WindowResizable, {

mode: 'stdout',

logUrl: computed('allocation.id', 'allocation.node.httpAddr', function() {
logUrl: computed('allocation.id', 'allocation.node.httpAddr', 'useServer', function() {
const address = this.get('allocation.node.httpAddr');
const allocation = this.get('allocation.id');

return `//${address}/v1/client/fs/logs/${allocation}`;
const url = `/v1/client/fs/logs/${allocation}`;
return this.get('useServer') ? url : `//${address}${url}`;
}),

logParams: computed('task', 'mode', function() {
@@ -51,9 +56,18 @@ export default Component.extend(WindowResizable, {
};
}),

logger: logger('logUrl', 'logParams', function() {
const token = this.get('token');
return token.authorizedRequest.bind(token);
logger: logger('logUrl', 'logParams', function logFetch() {
// If the log request can't settle in one second, the client
// must be unavailable and the server should be used instead
return url =>
RSVP.race([this.get('token').authorizedRequest(url), timeout(1000)]).then(
response => response,
error => {
this.send('failoverToServer');
this.get('stream').perform();
throw error;
}
);
}),

head: task(function*() {
@@ -100,5 +114,8 @@ export default Component.extend(WindowResizable, {
this.get('stream').perform();
}
},
failoverToServer() {
this.set('useServer', true);
},
},
});
9 changes: 6 additions & 3 deletions ui/app/utils/classes/log.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Ember from 'ember';
import { alias } from '@ember/object/computed';
import { assert } from '@ember/debug';
import Evented from '@ember/object/evented';
@@ -10,6 +11,8 @@ import PollLogger from 'nomad-ui/utils/classes/poll-logger';

const MAX_OUTPUT_LENGTH = 50000;

export const fetchFailure = url => () => Ember.Logger.warn(`LOG FETCH: Couldn't connect to ${url}`);

const Log = EmberObject.extend(Evented, {
// Parameters

@@ -74,9 +77,9 @@ const Log = EmberObject.extend(Evented, {
const url = `${this.get('url')}?${queryParams}`;

this.stop();
let text = yield logFetch(url).then(res => res.text());
let text = yield logFetch(url).then(res => res.text(), fetchFailure(url));

if (text.length > MAX_OUTPUT_LENGTH) {
if (text && text.length > MAX_OUTPUT_LENGTH) {
text = text.substr(0, MAX_OUTPUT_LENGTH);
text += '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------';
}
@@ -96,7 +99,7 @@ const Log = EmberObject.extend(Evented, {
const url = `${this.get('url')}?${queryParams}`;

this.stop();
let text = yield logFetch(url).then(res => res.text());
let text = yield logFetch(url).then(res => res.text(), fetchFailure(url));

this.set('tail', text);
this.set('logPointer', 'tail');
10 changes: 9 additions & 1 deletion ui/app/utils/classes/poll-logger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import EmberObject from '@ember/object';
import { task, timeout } from 'ember-concurrency';
import AbstractLogger from './abstract-logger';
import { fetchFailure } from './log';

export default EmberObject.extend(AbstractLogger, {
interval: 1000,
@@ -18,7 +19,14 @@ export default EmberObject.extend(AbstractLogger, {
poll: task(function*() {
const { interval, logFetch } = this.getProperties('interval', 'logFetch');
while (true) {
let text = yield logFetch(this.get('fullUrl')).then(res => res.text());
const url = this.get('fullUrl');
let response = yield logFetch(url).then(res => res, fetchFailure(url));

if (!response) {
return;
}

let text = yield response.text();

if (text) {
const lines = text.replace(/\}\{/g, '}\n{').split('\n');
7 changes: 6 additions & 1 deletion ui/app/utils/classes/stream-logger.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import EmberObject, { computed } from '@ember/object';
import { task } from 'ember-concurrency';
import TextDecoder from 'nomad-ui/utils/classes/text-decoder';
import AbstractLogger from './abstract-logger';
import { fetchFailure } from './log';

export default EmberObject.extend(AbstractLogger, {
reader: null,
@@ -30,7 +31,11 @@ export default EmberObject.extend(AbstractLogger, {
let buffer = '';

const decoder = new TextDecoder();
const reader = yield logFetch(url).then(res => res.body.getReader());
const reader = yield logFetch(url).then(res => res.body.getReader(), fetchFailure(url));

if (!reader) {
return;
}

this.set('reader', reader);

73 changes: 50 additions & 23 deletions ui/tests/integration/task-log-test.js
Original file line number Diff line number Diff line change
@@ -26,30 +26,33 @@ let streamPointer = 0;
moduleForComponent('task-log', 'Integration | Component | task log', {
integration: true,
beforeEach() {
const handler = ({ queryParams }) => {
const { origin, offset, plain, follow } = queryParams;

let frames;
let data;

if (origin === 'start' && offset === '0' && plain && !follow) {
frames = logHead;
} else if (origin === 'end' && plain && !follow) {
frames = logTail;
} else {
frames = streamFrames;
}

if (frames === streamFrames) {
data = queryParams.plain ? frames[streamPointer] : logEncode(frames, streamPointer);
streamPointer++;
} else {
data = queryParams.plain ? frames.join('') : logEncode(frames, frames.length - 1);
}

return [200, {}, data];
};

this.server = new Pretender(function() {
this.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, ({ queryParams }) => {
const { origin, offset, plain, follow } = queryParams;

let frames;
let data;

if (origin === 'start' && offset === '0' && plain && !follow) {
frames = logHead;
} else if (origin === 'end' && plain && !follow) {
frames = logTail;
} else {
frames = streamFrames;
}

if (frames === streamFrames) {
data = queryParams.plain ? frames[streamPointer] : logEncode(frames, streamPointer);
streamPointer++;
} else {
data = queryParams.plain ? frames.join('') : logEncode(frames, frames.length - 1);
}

return [200, {}, data];
});
this.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, handler);
this.get('/v1/client/fs/logs/:allocation_id', handler);
});
},
afterEach() {
@@ -174,3 +177,27 @@ test('Clicking stderr switches the log to standard error', function(assert) {
);
});
});

test('When the client is inaccessible, task-log falls back to requesting logs through the server', function(assert) {
run.later(run, run.cancelTimers, 2000);

// override client response to timeout
this.server.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, () => [400, {}, ''], 2000);

this.setProperties(commonProps);
this.render(hbs`{{task-log allocation=allocation task=task}}`);

const clientUrlRegex = new RegExp(`${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`);
assert.ok(
this.server.handledRequests.filter(req => clientUrlRegex.test(req.url)).length,
'Log request was initially made directly to the client'
);

return wait().then(() => {
const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`;
assert.ok(
this.server.handledRequests.filter(req => req.url.startsWith(serverUrl)).length,
'Log request was later made to the server'
);
});
});

0 comments on commit 5346f65

Please sign in to comment.