2019-07-02 21:42:38 +00:00
|
|
|
|
import { currentURL, visit } from '@ember/test-helpers';
|
|
|
|
|
import { Promise } from 'rsvp';
|
2019-06-20 22:20:15 +00:00
|
|
|
|
import { module, test } from 'qunit';
|
|
|
|
|
import { setupApplicationTest } from 'ember-qunit';
|
2019-07-26 05:44:29 +00:00
|
|
|
|
import moment from 'moment';
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
2019-06-20 22:20:15 +00:00
|
|
|
|
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
2019-07-02 21:42:38 +00:00
|
|
|
|
import Response from 'ember-cli-mirage/response';
|
|
|
|
|
|
2019-07-26 05:44:29 +00:00
|
|
|
|
import { formatBytes } from 'nomad-ui/helpers/format-bytes';
|
2019-07-31 08:41:00 +00:00
|
|
|
|
import { filesForPath } from 'nomad-ui/mirage/config';
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
2019-06-20 22:20:15 +00:00
|
|
|
|
import FS from 'nomad-ui/tests/pages/allocations/task/fs';
|
|
|
|
|
|
|
|
|
|
let allocation;
|
|
|
|
|
let task;
|
2019-07-26 05:44:29 +00:00
|
|
|
|
let files;
|
2019-06-20 22:20:15 +00:00
|
|
|
|
|
2019-07-31 08:41:00 +00:00
|
|
|
|
const fileSort = (prop, files) => {
|
|
|
|
|
let dir = [];
|
|
|
|
|
let file = [];
|
|
|
|
|
files.forEach(f => {
|
|
|
|
|
if (f.isDir) {
|
|
|
|
|
dir.push(f);
|
|
|
|
|
} else {
|
|
|
|
|
file.push(f);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return dir.sortBy(prop).concat(file.sortBy(prop));
|
|
|
|
|
};
|
2019-06-20 22:20:15 +00:00
|
|
|
|
|
|
|
|
|
module('Acceptance | task fs', function(hooks) {
|
|
|
|
|
setupApplicationTest(hooks);
|
|
|
|
|
setupMirage(hooks);
|
|
|
|
|
|
|
|
|
|
hooks.beforeEach(async function() {
|
|
|
|
|
server.create('agent');
|
|
|
|
|
server.create('node', 'forceIPv4');
|
|
|
|
|
const job = server.create('job', { createAllocations: false });
|
|
|
|
|
|
|
|
|
|
allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' });
|
|
|
|
|
task = server.schema.taskStates.where({ allocationId: allocation.id }).models[0];
|
2019-07-02 21:42:38 +00:00
|
|
|
|
task.name = 'task-name';
|
|
|
|
|
task.save();
|
2019-07-26 05:44:29 +00:00
|
|
|
|
|
|
|
|
|
// Reset files
|
|
|
|
|
files = [];
|
|
|
|
|
|
|
|
|
|
// Nested files
|
|
|
|
|
files.push(server.create('allocFile', { isDir: true, name: 'directory' }));
|
|
|
|
|
files.push(server.create('allocFile', { isDir: true, name: 'another', parent: files[0] }));
|
|
|
|
|
files.push(
|
|
|
|
|
server.create('allocFile', 'file', {
|
|
|
|
|
name: 'something.txt',
|
|
|
|
|
fileType: 'txt',
|
|
|
|
|
parent: files[1],
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
files.push(server.create('allocFile', { isDir: true, name: 'empty-directory' }));
|
2019-08-08 00:35:43 +00:00
|
|
|
|
files.push(server.create('allocFile', 'file', { fileType: 'txt' }));
|
|
|
|
|
files.push(server.create('allocFile', 'file', { fileType: 'txt' }));
|
2019-06-20 22:20:15 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('visiting /allocations/:allocation_id/:task_name/fs', async function(assert) {
|
|
|
|
|
await FS.visit({ id: allocation.id, name: task.name });
|
|
|
|
|
assert.equal(currentURL(), `/allocations/${allocation.id}/${task.name}/fs`, 'No redirect');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('when the task is not running, an empty state is shown', async function(assert) {
|
2019-09-04 22:38:26 +00:00
|
|
|
|
// The API 500s on stat when not running
|
|
|
|
|
this.server.get('/client/fs/stat/:allocation_id', () => {
|
|
|
|
|
return new Response(500, {}, 'no such file or directory');
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-20 22:20:15 +00:00
|
|
|
|
task.update({
|
|
|
|
|
finishedAt: new Date(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await FS.visit({ id: allocation.id, name: task.name });
|
|
|
|
|
assert.ok(FS.hasEmptyState, 'Non-running task has no files');
|
|
|
|
|
assert.ok(
|
|
|
|
|
FS.emptyState.headline.includes('Task is not Running'),
|
|
|
|
|
'Empty state explains the condition'
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
|
|
|
|
test('visiting /allocations/:allocation_id/:task_name/fs/:path', async function(assert) {
|
|
|
|
|
const paths = ['some-file.log', 'a/deep/path/to/a/file.log', '/', 'Unicode™®'];
|
|
|
|
|
|
|
|
|
|
const testPath = async filePath => {
|
2019-08-01 16:17:46 +00:00
|
|
|
|
let pathWithLeadingSlash = filePath;
|
|
|
|
|
|
|
|
|
|
if (!pathWithLeadingSlash.startsWith('/')) {
|
|
|
|
|
pathWithLeadingSlash = `/${filePath}`;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-02 21:42:38 +00:00
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: filePath });
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
`/allocations/${allocation.id}/${task.name}/fs/${encodeURIComponent(filePath)}`,
|
|
|
|
|
'No redirect'
|
|
|
|
|
);
|
2019-08-01 16:17:46 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
document.title,
|
|
|
|
|
`${pathWithLeadingSlash} - Task ${task.name} filesystem - Nomad`
|
|
|
|
|
);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.equal(FS.breadcrumbsText, `${task.name} ${filePath.replace(/\//g, ' ')}`.trim());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await paths.reduce(async (prev, filePath) => {
|
|
|
|
|
await prev;
|
|
|
|
|
return testPath(filePath);
|
|
|
|
|
}, Promise.resolve());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('navigating allocation filesystem', async function(assert) {
|
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/' });
|
|
|
|
|
|
2019-07-31 08:41:00 +00:00
|
|
|
|
const sortedFiles = fileSort('name', filesForPath(this.server.schema.allocFiles, '').models);
|
|
|
|
|
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.ok(FS.fileViewer.isHidden);
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.directoryEntries.length, 4);
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.breadcrumbsText, task.name);
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.breadcrumbs.length, 1);
|
|
|
|
|
assert.ok(FS.breadcrumbs[0].isActive);
|
|
|
|
|
assert.equal(FS.breadcrumbs[0].text, 'task-name');
|
|
|
|
|
|
|
|
|
|
FS.directoryEntries[0].as(directory => {
|
2019-07-31 08:41:00 +00:00
|
|
|
|
const fileRecord = sortedFiles[0];
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(directory.name, fileRecord.name, 'directories should come first');
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.ok(directory.isDirectory);
|
|
|
|
|
assert.equal(directory.size, '', 'directory sizes are hidden');
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(directory.lastModified, moment(fileRecord.modTime).fromNow());
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.notOk(directory.path.includes('//'), 'paths shouldn’t have redundant separators');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
FS.directoryEntries[2].as(file => {
|
2019-07-31 08:41:00 +00:00
|
|
|
|
const fileRecord = sortedFiles[2];
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(file.name, fileRecord.name);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.ok(file.isFile);
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(file.size, formatBytes([fileRecord.size]));
|
|
|
|
|
assert.equal(file.lastModified, moment(fileRecord.modTime).fromNow());
|
2019-07-02 21:42:38 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await FS.directoryEntries[0].visit();
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.directoryEntries.length, 1);
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.breadcrumbs.length, 2);
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(FS.breadcrumbsText, `${task.name} ${files[0].name}`);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
|
|
|
|
assert.notOk(FS.breadcrumbs[0].isActive);
|
|
|
|
|
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(FS.breadcrumbs[1].text, files[0].name);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.ok(FS.breadcrumbs[1].isActive);
|
|
|
|
|
|
|
|
|
|
await FS.directoryEntries[0].visit();
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.directoryEntries.length, 1);
|
|
|
|
|
assert.notOk(
|
|
|
|
|
FS.directoryEntries[0].path.includes('//'),
|
|
|
|
|
'paths shouldn’t have redundant separators'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.equal(FS.breadcrumbs.length, 3);
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(FS.breadcrumbsText, `${task.name} ${files[0].name} ${files[1].name}`);
|
|
|
|
|
assert.equal(FS.breadcrumbs[2].text, files[1].name);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
|
|
|
|
assert.notOk(
|
|
|
|
|
FS.breadcrumbs[0].path.includes('//'),
|
|
|
|
|
'paths shouldn’t have redundant separators'
|
|
|
|
|
);
|
|
|
|
|
assert.notOk(
|
|
|
|
|
FS.breadcrumbs[1].path.includes('//'),
|
|
|
|
|
'paths shouldn’t have redundant separators'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await FS.breadcrumbs[1].visit();
|
2019-07-26 05:44:29 +00:00
|
|
|
|
assert.equal(FS.breadcrumbsText, `${task.name} ${files[0].name}`);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.equal(FS.breadcrumbs.length, 2);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-23 20:37:34 +00:00
|
|
|
|
test('sorting allocation filesystem directory', async function(assert) {
|
|
|
|
|
this.server.get('/client/fs/ls/:allocation_id', () => {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
Name: 'aaa-big-old-file',
|
|
|
|
|
IsDir: false,
|
|
|
|
|
Size: 19190000,
|
|
|
|
|
ModTime: moment()
|
|
|
|
|
.subtract(1, 'year')
|
|
|
|
|
.format(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: 'mmm-small-mid-file',
|
|
|
|
|
IsDir: false,
|
|
|
|
|
Size: 1919,
|
|
|
|
|
ModTime: moment()
|
|
|
|
|
.subtract(6, 'month')
|
|
|
|
|
.format(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: 'zzz-med-new-file',
|
|
|
|
|
IsDir: false,
|
|
|
|
|
Size: 191900,
|
|
|
|
|
ModTime: moment().format(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: 'aaa-big-old-directory',
|
|
|
|
|
IsDir: true,
|
|
|
|
|
Size: 19190000,
|
|
|
|
|
ModTime: moment()
|
|
|
|
|
.subtract(1, 'year')
|
|
|
|
|
.format(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: 'mmm-small-mid-directory',
|
|
|
|
|
IsDir: true,
|
|
|
|
|
Size: 1919,
|
|
|
|
|
ModTime: moment()
|
|
|
|
|
.subtract(6, 'month')
|
|
|
|
|
.format(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: 'zzz-med-new-directory',
|
|
|
|
|
IsDir: true,
|
|
|
|
|
Size: 191900,
|
|
|
|
|
ModTime: moment().format(),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/' });
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(FS.directoryEntryNames(), [
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
await FS.sortBy('Name');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(FS.directoryEntryNames(), [
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
await FS.sortBy('ModTime');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(FS.directoryEntryNames(), [
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
await FS.sortBy('ModTime');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(FS.directoryEntryNames(), [
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
await FS.sortBy('Size');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
FS.directoryEntryNames(),
|
|
|
|
|
[
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
],
|
|
|
|
|
'expected files to be sorted by descending size and directories to be sorted by descending name'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await FS.sortBy('Size');
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
FS.directoryEntryNames(),
|
|
|
|
|
[
|
|
|
|
|
'aaa-big-old-directory',
|
|
|
|
|
'mmm-small-mid-directory',
|
|
|
|
|
'zzz-med-new-directory',
|
|
|
|
|
'mmm-small-mid-file',
|
|
|
|
|
'zzz-med-new-file',
|
|
|
|
|
'aaa-big-old-file',
|
|
|
|
|
],
|
|
|
|
|
'expected directories to be sorted by name and files to be sorted by ascending size'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-02 21:42:38 +00:00
|
|
|
|
test('viewing a file', async function(assert) {
|
2019-08-08 00:35:43 +00:00
|
|
|
|
const node = server.db.nodes.find(allocation.nodeId);
|
|
|
|
|
|
|
|
|
|
server.get(`http://${node.httpAddr}/v1/client/fs/readat/:allocation_id`, function() {
|
|
|
|
|
return new Response(500);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-02 21:42:38 +00:00
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/' });
|
|
|
|
|
|
2019-07-31 08:41:00 +00:00
|
|
|
|
const sortedFiles = fileSort('name', filesForPath(this.server.schema.allocFiles, '').models);
|
|
|
|
|
const fileRecord = sortedFiles.find(f => !f.isDir);
|
|
|
|
|
const fileIndex = sortedFiles.indexOf(fileRecord);
|
|
|
|
|
|
|
|
|
|
await FS.directoryEntries[fileIndex].visit();
|
2019-07-26 05:44:29 +00:00
|
|
|
|
|
|
|
|
|
assert.equal(FS.breadcrumbsText, `${task.name} ${fileRecord.name}`);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
|
|
|
|
|
assert.ok(FS.fileViewer.isPresent);
|
2019-08-08 00:35:43 +00:00
|
|
|
|
|
|
|
|
|
const requests = this.server.pretender.handledRequests;
|
|
|
|
|
const secondAttempt = requests.pop();
|
|
|
|
|
const firstAttempt = requests.pop();
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
firstAttempt.url.split('?')[0],
|
|
|
|
|
`//${node.httpAddr}/v1/client/fs/readat/${allocation.id}`,
|
|
|
|
|
'Client is hit first'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(firstAttempt.status, 500, 'Client request fails');
|
|
|
|
|
assert.equal(
|
|
|
|
|
secondAttempt.url.split('?')[0],
|
|
|
|
|
`/v1/client/fs/readat/${allocation.id}`,
|
|
|
|
|
'Server is hit second'
|
|
|
|
|
);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('viewing an empty directory', async function(assert) {
|
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/empty-directory' });
|
|
|
|
|
|
2019-08-20 00:16:26 +00:00
|
|
|
|
assert.ok(FS.isEmptyDirectory);
|
2019-07-02 21:42:38 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('viewing paths that produce stat API errors', async function(assert) {
|
|
|
|
|
this.server.get('/client/fs/stat/:allocation_id', () => {
|
|
|
|
|
return new Response(500, {}, 'no such file or directory');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/what-is-this' });
|
|
|
|
|
assert.equal(FS.error.title, 'Not Found', '500 is interpreted as 404');
|
|
|
|
|
|
|
|
|
|
await visit('/');
|
|
|
|
|
|
|
|
|
|
this.server.get('/client/fs/stat/:allocation_id', () => {
|
|
|
|
|
return new Response(999);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: '/what-is-this' });
|
|
|
|
|
assert.equal(FS.error.title, 'Error', 'other statuses are passed through');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('viewing paths that produce ls API errors', async function(assert) {
|
|
|
|
|
this.server.get('/client/fs/ls/:allocation_id', () => {
|
|
|
|
|
return new Response(500, {}, 'no such file or directory');
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-26 05:44:29 +00:00
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: files[0].name });
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.equal(FS.error.title, 'Not Found', '500 is interpreted as 404');
|
|
|
|
|
|
|
|
|
|
await visit('/');
|
|
|
|
|
|
|
|
|
|
this.server.get('/client/fs/ls/:allocation_id', () => {
|
|
|
|
|
return new Response(999);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-26 05:44:29 +00:00
|
|
|
|
await FS.visitPath({ id: allocation.id, name: task.name, path: files[0].name });
|
2019-07-02 21:42:38 +00:00
|
|
|
|
assert.equal(FS.error.title, 'Error', 'other statuses are passed through');
|
|
|
|
|
});
|
2019-06-20 22:20:15 +00:00
|
|
|
|
});
|