Fallback to using the nomad server for log streaming
Only when the client isn't accessible
This commit is contained in:
parent
470b8131bd
commit
dc72ac2bc7
|
@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@ let streamPointer = 0;
|
|||
moduleForComponent('task-log', 'Integration | Component | task log', {
|
||||
integration: true,
|
||||
beforeEach() {
|
||||
this.server = new Pretender(function() {
|
||||
this.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, ({ queryParams }) => {
|
||||
const handler = ({ queryParams }) => {
|
||||
const { origin, offset, plain, follow } = queryParams;
|
||||
|
||||
let frames;
|
||||
|
@ -49,7 +48,11 @@ moduleForComponent('task-log', 'Integration | Component | task log', {
|
|||
}
|
||||
|
||||
return [200, {}, data];
|
||||
});
|
||||
};
|
||||
|
||||
this.server = new Pretender(function() {
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue