Skip to content

Commit

Permalink
UI: add filesystem browsing for allocations (#7951)
Browse files Browse the repository at this point in the history
This partially addresses #7799.

Task state filesystems are contained within a subdirectory of their
parent allocation, so almost everything that existed for browsing task
state filesystems was applicable to browsing allocations, just without
the task name prepended to the path. I aimed to push this differential
handling into as few contained places as possible.

The tests also have significant overlap, so this includes an extracted
behavior to run the same tests for allocations and task states.
  • Loading branch information
backspace committed Jun 1, 2020
1 parent 3b04afe commit 06baadd
Show file tree
Hide file tree
Showing 34 changed files with 786 additions and 491 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ IMPROVEMENTS:
* csi: Move volume claim releases out of evaluation workers [[GH-8021](https://github.com/hashicorp/nomad/issues/8021)]
* csi: Added support for `VolumeContext` and `VolumeParameters` [[GH-7957](https://github.com/hashicorp/nomad/issues/7957)]
* logging: Remove spurious error log on task shutdown [[GH-8028](https://github.com/hashicorp/nomad/issues/8028)]
* ui: Added filesystem browsing for allocations [[GH-5871](https://github.com/hashicorp/nomad/pull/7951)]

BUG FIXES:

Expand Down
33 changes: 33 additions & 0 deletions ui/app/adapters/allocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,41 @@ export default Watchable.extend({
data: taskName && { TaskName: taskName },
});
},

ls(model, path) {
return this.token
.authorizedRequest(`/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}`)
.then(handleFSResponse);
},

stat(model, path) {
return this.token
.authorizedRequest(
`/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}`
)
.then(handleFSResponse);
},
});

async function handleFSResponse(response) {
if (response.ok) {
return response.json();
} else {
const body = await response.text();

// TODO update this if/when endpoint returns 404 as expected
const statusIs500 = response.status === 500;
const bodyIncludes404Text = body.includes('no such file or directory');

const translatedCode = statusIs500 && bodyIncludes404Text ? 404 : response.status;

throw {
code: translatedCode,
toString: () => body,
};
}
}

function adapterAction(path, verb = 'POST') {
return function(allocation) {
const url = addToPath(this.urlForFindRecord(allocation.id, 'allocation'), path);
Expand Down
39 changes: 0 additions & 39 deletions ui/app/adapters/task-state.js

This file was deleted.

14 changes: 14 additions & 0 deletions ui/app/components/allocation-subnav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { equal, or } from '@ember/object/computed';

export default Component.extend({
router: service(),

tagName: '',

fsIsActive: equal('router.currentRouteName', 'allocations.allocation.fs'),
fsRootIsActive: equal('router.currentRouteName', 'allocations.allocation.fs-root'),

filesLinkActive: or('fsIsActive', 'fsRootIsActive'),
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default Component.extend({

'data-test-fs-breadcrumbs': true,

allocation: null,
task: null,
path: null,

Expand Down
57 changes: 57 additions & 0 deletions ui/app/components/fs/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { filterBy } from '@ember/object/computed';

export default Component.extend({
tagName: '',

model: null,

allocation: computed('model', function() {
if (this.model.allocation) {
return this.model.allocation;
} else {
return this.model;
}
}),

task: computed('model', function() {
if (this.model.allocation) {
return this.model;
}
}),

type: computed('task', function() {
if (this.task) {
return 'task';
} else {
return 'allocation';
}
}),

directories: filterBy('directoryEntries', 'IsDir'),
files: filterBy('directoryEntries', 'IsDir', false),

sortedDirectoryEntries: computed(
'directoryEntries.[]',
'sortProperty',
'sortDescending',
function() {
const sortProperty = this.sortProperty;

const directorySortProperty = sortProperty === 'Size' ? 'Name' : sortProperty;

const sortedDirectories = this.directories.sortBy(directorySortProperty);
const sortedFiles = this.files.sortBy(sortProperty);

const sortedDirectoryEntries = sortedDirectories.concat(sortedFiles);

if (this.sortDescending) {
return sortedDirectoryEntries.reverse();
} else {
return sortedDirectoryEntries;
}
}
),

});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { isEmpty } from '@ember/utils';
export default Component.extend({
tagName: '',

allocation: null,
task: null,

pathToEntry: computed('path', 'entry.Name', function() {
const pathWithNoLeadingSlash = this.get('path').replace(/^\//, '');
const name = encodeURIComponent(this.get('entry.Name'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export default Component.extend({
isStreaming: false,

catUrl: computed('allocation.id', 'task.name', 'file', function() {
const encodedPath = encodeURIComponent(`${this.task.name}/${this.file}`);
const taskUrlPrefix = this.task ? `${this.task.name}/` : '';
const encodedPath = encodeURIComponent(`${taskUrlPrefix}${this.file}`);
return `/v1/client/fs/cat/${this.allocation.id}?path=${encodedPath}`;
}),

Expand Down Expand Up @@ -79,7 +80,8 @@ export default Component.extend({

fileParams: computed('task.name', 'file', 'mode', function() {
// The Log class handles encoding query params
const path = `${this.task.name}/${this.file}`;
const taskUrlPrefix = this.task ? `${this.task.name}/` : '';
const path = `${taskUrlPrefix}${this.file}`;

switch (this.mode) {
case 'head':
Expand Down
8 changes: 8 additions & 0 deletions ui/app/components/fs/link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Component from '@ember/component';

export default Component.extend({
tagName: '',

allocation: null,
task: null,
});
3 changes: 3 additions & 0 deletions ui/app/controllers/allocations/allocation/fs-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FSController from './fs';

export default FSController.extend();
28 changes: 28 additions & 0 deletions ui/app/controllers/allocations/allocation/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';

export default Controller.extend({
queryParams: {
sortProperty: 'sort',
sortDescending: 'desc',
},

sortProperty: 'Name',
sortDescending: false,

path: null,
allocation: null,
directoryEntries: null,
isFile: null,
stat: null,

pathWithLeadingSlash: computed('path', function() {
const path = this.path;

if (path.startsWith('/')) {
return path;
} else {
return `/${path}`;
}
}),
});
26 changes: 0 additions & 26 deletions ui/app/controllers/allocations/allocation/task/fs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { filterBy } from '@ember/object/computed';

export default Controller.extend({
queryParams: {
Expand All @@ -17,9 +16,6 @@ export default Controller.extend({
isFile: null,
stat: null,

directories: filterBy('directoryEntries', 'IsDir'),
files: filterBy('directoryEntries', 'IsDir', false),

pathWithLeadingSlash: computed('path', function() {
const path = this.path;

Expand All @@ -29,26 +25,4 @@ export default Controller.extend({
return `/${path}`;
}
}),

sortedDirectoryEntries: computed(
'directoryEntries.[]',
'sortProperty',
'sortDescending',
function() {
const sortProperty = this.sortProperty;

const directorySortProperty = sortProperty === 'Size' ? 'Name' : sortProperty;

const sortedDirectories = this.directories.sortBy(directorySortProperty);
const sortedFiles = this.files.sortBy(sortProperty);

const sortedDirectoryEntries = sortedDirectories.concat(sortedFiles);

if (this.sortDescending) {
return sortedDirectoryEntries.reverse();
} else {
return sortedDirectoryEntries;
}
}
),
});
8 changes: 8 additions & 0 deletions ui/app/models/allocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,12 @@ export default Model.extend({
restart(taskName) {
return this.store.adapterFor('allocation').restart(this, taskName);
},

ls(path) {
return this.store.adapterFor('allocation').ls(this, path);
},

stat(path) {
return this.store.adapterFor('allocation').stat(this, path);
},
});
8 changes: 0 additions & 8 deletions ui/app/models/task-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,4 @@ export default Fragment.extend({
restart() {
return this.allocation.restart(this.name);
},

ls(path) {
return this.store.adapterFor('task-state').ls(this, path);
},

stat(path) {
return this.store.adapterFor('task-state').stat(this, path);
},
});
3 changes: 3 additions & 0 deletions ui/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Router.map(function() {

this.route('allocations', function() {
this.route('allocation', { path: '/:allocation_id' }, function() {
this.route('fs-root', { path: '/fs' });
this.route('fs', { path: '/fs/*path' });

this.route('task', { path: '/:name' }, function() {
this.route('logs');
this.route('fs-root', { path: '/fs' });
Expand Down
5 changes: 5 additions & 0 deletions ui/app/routes/allocations/allocation/fs-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import FSRoute from './fs';

export default FSRoute.extend({
templateName: 'allocations/allocation/fs',
});
42 changes: 42 additions & 0 deletions ui/app/routes/allocations/allocation/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Route from '@ember/routing/route';
import RSVP from 'rsvp';
import notifyError from 'nomad-ui/utils/notify-error';

export default Route.extend({
model({ path = '/' }) {
const decodedPath = decodeURIComponent(path);
const allocation = this.modelFor('allocations.allocation');

if (!allocation.isRunning) {
return {
path: decodedPath,
allocation,
};
}

return RSVP.all([allocation.stat(decodedPath), allocation.get('node')])
.then(([statJson]) => {
if (statJson.IsDir) {
return RSVP.hash({
path: decodedPath,
allocation,
directoryEntries: allocation.ls(decodedPath).catch(notifyError(this)),
isFile: false,
});
} else {
return {
path: decodedPath,
allocation,
isFile: true,
stat: statJson,
};
}
})
.catch(notifyError(this));
},

setupController(controller, { path, allocation, directoryEntries, isFile, stat } = {}) {
this._super(...arguments);
controller.setProperties({ path, allocation, directoryEntries, isFile, stat });
},
});
5 changes: 3 additions & 2 deletions ui/app/routes/allocations/allocation/task/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default Route.extend({
model({ path = '/' }) {
const decodedPath = decodeURIComponent(path);
const task = this.modelFor('allocations.allocation.task');
const allocation = task.allocation;

const pathWithTaskName = `${task.name}${decodedPath.startsWith('/') ? '' : '/'}${decodedPath}`;

Expand All @@ -16,13 +17,13 @@ export default Route.extend({
};
}

return RSVP.all([task.stat(pathWithTaskName), task.get('allocation.node')])
return RSVP.all([allocation.stat(pathWithTaskName), task.get('allocation.node')])
.then(([statJson]) => {
if (statJson.IsDir) {
return RSVP.hash({
path: decodedPath,
task,
directoryEntries: task.ls(pathWithTaskName).catch(notifyError(this)),
directoryEntries: allocation.ls(pathWithTaskName).catch(notifyError(this)),
isFile: false,
});
} else {
Expand Down
Loading

0 comments on commit 06baadd

Please sign in to comment.