UI: add Consul Connect features (#6108)
This commit is contained in:
parent
6d73ca0cfb
commit
b5e5798e54
|
@ -8,6 +8,7 @@ IMPROVEMENTS:
|
|||
* api: Added follow parameter to file streaming endpoint to support older browsers [[GH-6049](https://github.com/hashicorp/nomad/issues/6049)]
|
||||
* cli: Added `-dev-connect` parameter to support running in dev mode with Consul Connect [[GH-6126](https://github.com/hashicorp/nomad/issues/6126)]
|
||||
* metrics: Add job status (pending, running, dead) metrics [[GH-6003](https://github.com/hashicorp/nomad/issues/6003)]
|
||||
* ui: Added Consul Connect features [[GH-6108](https://github.com/hashicorp/nomad/pull/6108)]
|
||||
* ui: Added creation time to evaluations table [[GH-6050](https://github.com/hashicorp/nomad/pull/6050)]
|
||||
|
||||
BUG FIXES:
|
||||
|
|
5
ui/app/components/proxy-tag.js
Normal file
5
ui/app/components/proxy-tag.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
|
@ -29,6 +29,12 @@ export default Controller.extend(Sortable, {
|
|||
return null;
|
||||
}),
|
||||
|
||||
network: alias('model.allocatedResources.networks.firstObject'),
|
||||
|
||||
services: computed('model.taskGroup.services.@each.name', function() {
|
||||
return this.get('model.taskGroup.services').sortBy('name');
|
||||
}),
|
||||
|
||||
onDismiss() {
|
||||
this.set('error', null);
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ export default Model.extend({
|
|||
name: attr('string'),
|
||||
taskGroupName: attr('string'),
|
||||
resources: fragment('resources'),
|
||||
allocatedResources: fragment('resources'),
|
||||
jobVersion: attr('number'),
|
||||
|
||||
modifyIndex: attr('number'),
|
||||
|
|
6
ui/app/models/consul-connect.js
Normal file
6
ui/app/models/consul-connect.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default Fragment.extend({
|
||||
sidecarService: fragment('sidecar-service'),
|
||||
});
|
|
@ -6,7 +6,7 @@ export default Fragment.extend({
|
|||
device: attr('string'),
|
||||
cidr: attr('string'),
|
||||
ip: attr('string'),
|
||||
mode: attr('string'),
|
||||
mbits: attr('number'),
|
||||
reservedPorts: array(),
|
||||
dynamicPorts: array(),
|
||||
ports: array(),
|
||||
});
|
||||
|
|
10
ui/app/models/service.js
Normal file
10
ui/app/models/service.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import attr from 'ember-data/attr';
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default Fragment.extend({
|
||||
name: attr('string'),
|
||||
portLabel: attr('string'),
|
||||
tags: attr(),
|
||||
connect: fragment('consul-connect'),
|
||||
});
|
7
ui/app/models/sidecar-proxy-upstream.js
Normal file
7
ui/app/models/sidecar-proxy-upstream.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export default Fragment.extend({
|
||||
destinationName: attr('string'),
|
||||
localBindPort: attr('string'),
|
||||
});
|
6
ui/app/models/sidecar-proxy.js
Normal file
6
ui/app/models/sidecar-proxy.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default Fragment.extend({
|
||||
upstreams: fragmentArray('sidecar-proxy-upstream'),
|
||||
});
|
6
ui/app/models/sidecar-service.js
Normal file
6
ui/app/models/sidecar-service.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default Fragment.extend({
|
||||
proxy: fragment('sidecar-proxy'),
|
||||
});
|
|
@ -14,10 +14,10 @@ export default Fragment.extend({
|
|||
|
||||
tasks: fragmentArray('task'),
|
||||
|
||||
services: fragmentArray('service'),
|
||||
|
||||
drivers: computed('tasks.@each.driver', function() {
|
||||
return this.tasks
|
||||
.mapBy('driver')
|
||||
.uniq();
|
||||
return this.tasks.mapBy('driver').uniq();
|
||||
}),
|
||||
|
||||
allocations: computed('job.allocations.@each.taskGroup', function() {
|
||||
|
|
|
@ -9,6 +9,7 @@ export default Fragment.extend({
|
|||
|
||||
name: attr('string'),
|
||||
state: attr('string'),
|
||||
kind: attr('string'),
|
||||
startedAt: attr('date'),
|
||||
finishedAt: attr('date'),
|
||||
failed: attr('boolean'),
|
||||
|
@ -16,6 +17,10 @@ export default Fragment.extend({
|
|||
isActive: none('finishedAt'),
|
||||
isRunning: and('isActive', 'allocation.isRunning'),
|
||||
|
||||
isConnectProxy: computed('kind', function() {
|
||||
return (this.kind || '').startsWith('connect-proxy:');
|
||||
}),
|
||||
|
||||
task: computed('allocation.taskGroup.tasks.[]', function() {
|
||||
const tasks = this.get('allocation.taskGroup.tasks');
|
||||
return tasks && tasks.findBy('name', this.name);
|
||||
|
|
|
@ -49,6 +49,9 @@ export default ApplicationSerializer.extend({
|
|||
hash.PreemptedByAllocationID = hash.PreemptedByAllocation || null;
|
||||
hash.WasPreempted = !!hash.PreemptedByAllocationID;
|
||||
|
||||
// When present, the resources are nested under AllocatedResources.Shared
|
||||
hash.AllocatedResources = hash.AllocatedResources && hash.AllocatedResources.Shared;
|
||||
|
||||
return this._super(typeHash, hash);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,6 +15,22 @@ export default ApplicationSerializer.extend({
|
|||
hash.IP = `[${ip}]`;
|
||||
}
|
||||
|
||||
const reservedPorts = (hash.ReservedPorts || []).map(port => ({
|
||||
name: port.Label,
|
||||
port: port.Value,
|
||||
to: port.To,
|
||||
isDynamic: false,
|
||||
}));
|
||||
|
||||
const dynamicPorts = (hash.DynamicPorts || []).map(port => ({
|
||||
name: port.Label,
|
||||
port: port.Value,
|
||||
to: port.To,
|
||||
isDynamic: true,
|
||||
}));
|
||||
|
||||
hash.Ports = reservedPorts.concat(dynamicPorts).sortBy('name');
|
||||
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
|
15
ui/app/serializers/service.js
Normal file
15
ui/app/serializers/service.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
attrs: {
|
||||
connect: 'Connect',
|
||||
},
|
||||
|
||||
normalize(typeHash, hash) {
|
||||
if (!hash.Tags) {
|
||||
hash.Tags = [];
|
||||
}
|
||||
|
||||
return this._super(typeHash, hash);
|
||||
},
|
||||
});
|
|
@ -115,6 +115,66 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if network.ports.length}}
|
||||
<div class="boxed-section" data-test-allocation-ports>
|
||||
<div class="boxed-section-head">
|
||||
Ports
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
{{#list-table source=network.ports as |t|}}
|
||||
{{#t.head}}
|
||||
<th class="is-2">Name</th>
|
||||
<th class="is-1">Dynamic?</th>
|
||||
<th>Host Address</th>
|
||||
<th>Mapped Port</th>
|
||||
{{/t.head}}
|
||||
{{#t.body as |row|}}
|
||||
<tr data-test-allocation-port>
|
||||
<td data-test-allocation-port-name>{{row.model.name}}</td>
|
||||
<td data-test-allocation-port-is-dynamic>{{if row.model.isDynamic "Yes" "No"}}</td>
|
||||
<td data-test-allocation-port-address>
|
||||
<a href="http://{{network.ip}}:{{row.model.port}}" target="_blank" rel="noopener noreferrer">{{network.ip}}:{{row.model.port}}</a>
|
||||
</td>
|
||||
<td data-test-allocation-port-to>{{row.model.to}}</td>
|
||||
</tr>
|
||||
{{/t.body}}
|
||||
{{/list-table}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if services.length}}
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Services
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
{{#list-table source=services as |t|}}
|
||||
{{#t.head}}
|
||||
<th class="is-2">Name</th>
|
||||
<th class="is-1">Port</th>
|
||||
<td>Tags</td>
|
||||
<td>Connect?</td>
|
||||
<td>Upstreams</td>
|
||||
{{/t.head}}
|
||||
{{#t.body as |row|}}
|
||||
<tr data-test-service>
|
||||
<td data-test-service-name>{{row.model.name}}</td>
|
||||
<td data-test-service-port>{{row.model.portLabel}}</td>
|
||||
<td data-test-service-tags>{{join ", " row.model.tags}}</td>
|
||||
<td data-test-service-connect>{{if row.model.connect "Yes" "No"}}</td>
|
||||
<td data-test-service-upstreams>
|
||||
{{#each row.model.connect.sidecarService.proxy.upstreams as |upstream|}}
|
||||
{{upstream.destinationName}}:{{upstream.localBindPort}}
|
||||
{{/each}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/t.body}}
|
||||
{{/list-table}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if model.hasRescheduleEvents}}
|
||||
<div class="boxed-section" data-test-reschedule-events>
|
||||
<div class="boxed-section-head is-hollow">
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
<h1 class="title" data-test-title>
|
||||
{{model.name}}
|
||||
<span class="bumper-left tag {{model.stateClass}}" data-test-state>{{model.state}}</span>
|
||||
{{#if model.isConnectProxy}}
|
||||
{{proxy-tag class="bumper-left"}}
|
||||
{{/if}}
|
||||
<span class="{{unless model.isConnectProxy "bumper-left"}} tag {{model.stateClass}}" data-test-state>{{model.state}}</span>
|
||||
{{#if model.isRunning}}
|
||||
{{two-step-button
|
||||
data-test-restart
|
||||
|
@ -74,13 +77,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if ports.length}}
|
||||
{{#if network.ports.length}}
|
||||
<div class="boxed-section" data-test-task-addresses>
|
||||
<div class="boxed-section-head">
|
||||
Addresses
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
{{#list-table source=ports as |t|}}
|
||||
{{#list-table source=network.ports as |t|}}
|
||||
{{#t.head}}
|
||||
<th class="is-1">Dynamic?</th>
|
||||
<th class="is-2">Name</th>
|
||||
|
|
3
ui/app/templates/components/freestyle/sg-proxy-tag.hbs
Normal file
3
ui/app/templates/components/freestyle/sg-proxy-tag.hbs
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{#freestyle-usage "proxy-tag" title="Proxy Tag"}}
|
||||
{{proxy-tag}}
|
||||
{{/freestyle-usage}}
|
3
ui/app/templates/components/proxy-tag.hbs
Normal file
3
ui/app/templates/components/proxy-tag.hbs
Normal file
|
@ -0,0 +1,3 @@
|
|||
<span class="badge is-light tooltip" role="tooltip" aria-label="Consul Connect proxy task" data-test-proxy-tag>
|
||||
Proxy
|
||||
</span>
|
|
@ -5,9 +5,12 @@
|
|||
</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td data-test-name>
|
||||
<td data-test-name class="nowrap">
|
||||
{{#link-to "allocations.allocation.task" task.allocation task class="is-primary"}}
|
||||
{{task.name}}
|
||||
{{#if task.isConnectProxy}}
|
||||
{{proxy-tag class="bumper-left"}}
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</td>
|
||||
<td data-test-state>{{task.state}}</td>
|
||||
|
@ -22,16 +25,10 @@
|
|||
<td data-test-ports>
|
||||
<ul>
|
||||
{{#with task.resources.networks.firstObject as |network|}}
|
||||
{{#each network.reservedPorts as |port|}}
|
||||
{{#each network.ports as |port|}}
|
||||
<li data-test-port>
|
||||
<strong>{{port.Label}}:</strong>
|
||||
<a href="http://{{network.ip}}:{{port.Value}}" target="_blank" rel="noopener noreferrer">{{network.ip}}:{{port.Value}}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#each network.dynamicPorts as |port|}}
|
||||
<li>
|
||||
<strong>{{port.Label}}:</strong>
|
||||
<a href="http://{{network.ip}}:{{port.Value}}" target="_blank" rel="noopener noreferrer">{{network.ip}}:{{port.Value}}</a>
|
||||
<strong>{{port.name}}:</strong>
|
||||
<a href="http://{{network.ip}}:{{port.port}}" target="_blank" rel="noopener noreferrer">{{network.ip}}:{{port.port}}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{/with}}
|
||||
|
|
|
@ -83,6 +83,10 @@
|
|||
{{freestyle/sg-page-title}}
|
||||
{{/section.subsection}}
|
||||
|
||||
{{#section.subsection name="Proxy Tag"}}
|
||||
{{freestyle/sg-proxy-tag}}
|
||||
{{/section.subsection}}
|
||||
|
||||
{{#section.subsection name="Search box"}}
|
||||
{{freestyle/sg-search-box}}
|
||||
{{/section.subsection}}
|
||||
|
|
|
@ -11,6 +11,8 @@ const IOPS_RESERVATIONS = [100000, 250000, 500000, 1000000, 10000000, 20000000];
|
|||
IOPS_RESERVATIONS.push(...Array(1000).fill(0));
|
||||
DISK_RESERVATIONS.push(...Array(500).fill(0));
|
||||
|
||||
const NETWORK_MODES = ['bridge', 'host'];
|
||||
|
||||
export const DATACENTERS = provide(
|
||||
15,
|
||||
(n, i) => `${faker.address.countryCode().toLowerCase()}${i}`
|
||||
|
@ -39,6 +41,7 @@ export function generateNetworks(options = {}) {
|
|||
CIDR: '',
|
||||
IP: faker.internet.ip(),
|
||||
MBits: 10,
|
||||
Mode: faker.random.arrayElement(NETWORK_MODES),
|
||||
ReservedPorts: Array(
|
||||
faker.random.number({
|
||||
min: options.minPorts != null ? options.minPorts : 0,
|
||||
|
@ -49,6 +52,7 @@ export function generateNetworks(options = {}) {
|
|||
.map(() => ({
|
||||
Label: faker.hacker.noun(),
|
||||
Value: faker.random.number({ min: 5000, max: 60000 }),
|
||||
To: faker.random.number({ min: 5000, max: 60000 }),
|
||||
})),
|
||||
DynamicPorts: Array(
|
||||
faker.random.number({
|
||||
|
@ -60,6 +64,7 @@ export function generateNetworks(options = {}) {
|
|||
.map(() => ({
|
||||
Label: faker.hacker.noun(),
|
||||
Value: faker.random.number({ min: 5000, max: 60000 }),
|
||||
To: faker.random.number({ min: 5000, max: 60000 }),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import Ember from 'ember';
|
|||
import moment from 'moment';
|
||||
import { Factory, faker, trait } from 'ember-cli-mirage';
|
||||
import { provide, pickOne } from '../utils';
|
||||
import { generateResources } from '../common';
|
||||
|
||||
const UUIDS = provide(100, faker.random.uuid.bind(faker.random));
|
||||
const CLIENT_STATUSES = ['pending', 'running', 'complete', 'failed', 'lost'];
|
||||
|
@ -65,6 +66,14 @@ export default Factory.extend({
|
|||
},
|
||||
}),
|
||||
|
||||
withAllocatedResources: trait({
|
||||
allocatedResources: () => {
|
||||
return {
|
||||
Shared: generateResources({ networks: { minPorts: 2 } }),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
rescheduleAttempts: 0,
|
||||
rescheduleSuccess: false,
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ export default Factory.extend({
|
|||
// When true, allocations for this job will fail and reschedule, randomly succeeding or not
|
||||
withRescheduling: false,
|
||||
|
||||
// When true, task groups will have services
|
||||
withGroupServices: false,
|
||||
|
||||
// When true, only task groups and allocations are made
|
||||
shallow: false,
|
||||
|
||||
|
@ -118,6 +121,7 @@ export default Factory.extend({
|
|||
job,
|
||||
createAllocations: job.createAllocations,
|
||||
withRescheduling: job.withRescheduling,
|
||||
withServices: job.withGroupServices,
|
||||
shallow: job.shallow,
|
||||
});
|
||||
|
||||
|
|
29
ui/mirage/factories/service.js
Normal file
29
ui/mirage/factories/service.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Factory, faker } from 'ember-cli-mirage';
|
||||
import { provide } from '../utils';
|
||||
|
||||
export default Factory.extend({
|
||||
name: id => `${faker.hacker.noun().dasherize()}-${id}-service`,
|
||||
portLabel: () => faker.hacker.noun().dasherize(),
|
||||
tags: () => {
|
||||
if (!faker.random.boolean()) {
|
||||
return provide(
|
||||
faker.random.number({ min: 0, max: 2 }),
|
||||
faker.hacker.noun.bind(faker.hacker.noun)
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
Connect: {
|
||||
SidecarService: {
|
||||
Proxy: {
|
||||
Upstreams: [
|
||||
{
|
||||
DestinationName: faker.hacker.noun().dasherize(),
|
||||
LocalBindPort: faker.random.number({ min: 5000, max: 60000 }),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
|
@ -20,6 +20,9 @@ export default Factory.extend({
|
|||
// and reschedule, creating reschedule events.
|
||||
withRescheduling: false,
|
||||
|
||||
// Directive used to control whether the task group should have services.
|
||||
withServices: false,
|
||||
|
||||
// When true, only creates allocations
|
||||
shallow: false,
|
||||
|
||||
|
@ -60,5 +63,15 @@ export default Factory.extend({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (group.withServices) {
|
||||
Array(faker.random.number({ min: 1, max: 3 }))
|
||||
.fill(null)
|
||||
.forEach(() => {
|
||||
server.create('service', {
|
||||
task_group: group,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ const REF_TIME = new Date();
|
|||
export default Factory.extend({
|
||||
name: () => '!!!this should be set by the allocation that owns this task state!!!',
|
||||
state: faker.list.random(...TASK_STATUSES),
|
||||
kind: null,
|
||||
startedAt: faker.date.past(2 / 365, REF_TIME),
|
||||
finishedAt() {
|
||||
if (['pending', 'running'].includes(this.state)) {
|
||||
|
|
5
ui/mirage/models/service.js
Normal file
5
ui/mirage/models/service.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Model, belongsTo } from 'ember-cli-mirage';
|
||||
|
||||
export default Model.extend({
|
||||
task_group: belongsTo('task-group'),
|
||||
});
|
|
@ -2,5 +2,6 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
|
|||
|
||||
export default Model.extend({
|
||||
job: belongsTo(),
|
||||
services: hasMany(),
|
||||
tasks: hasMany(),
|
||||
});
|
||||
|
|
|
@ -2,5 +2,5 @@ import ApplicationSerializer from './application';
|
|||
|
||||
export default ApplicationSerializer.extend({
|
||||
embed: true,
|
||||
include: ['tasks'],
|
||||
include: ['services', 'tasks'],
|
||||
});
|
||||
|
|
|
@ -19,8 +19,14 @@ module('Acceptance | allocation detail', function(hooks) {
|
|||
server.create('agent');
|
||||
|
||||
node = server.create('node');
|
||||
job = server.create('job', { groupsCount: 1, createAllocations: false });
|
||||
allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' });
|
||||
job = server.create('job', {
|
||||
groupsCount: 1,
|
||||
withGroupServices: true,
|
||||
createAllocations: false,
|
||||
});
|
||||
allocation = server.create('allocation', 'withTaskWithPorts', 'withAllocatedResources', {
|
||||
clientStatus: 'running',
|
||||
});
|
||||
|
||||
// Make sure the node has an unhealthy driver
|
||||
node.update({
|
||||
|
@ -134,6 +140,21 @@ module('Acceptance | allocation detail', function(hooks) {
|
|||
assert.ok(Allocation.firstUnhealthyTask().hasUnhealthyDriver, 'Warning is shown');
|
||||
});
|
||||
|
||||
test('proxy task has a proxy tag', async function(assert) {
|
||||
allocation = server.create('allocation', 'withTaskWithPorts', 'withAllocatedResources', {
|
||||
clientStatus: 'running',
|
||||
});
|
||||
|
||||
allocation.task_states.models.forEach(task => {
|
||||
task.kind = 'connect-proxy:task';
|
||||
task.save();
|
||||
});
|
||||
|
||||
await Allocation.visit({ id: allocation.id });
|
||||
|
||||
assert.ok(Allocation.tasks[0].hasProxyTag);
|
||||
});
|
||||
|
||||
test('when there are no tasks, an empty state is shown', async function(assert) {
|
||||
// Make sure the allocation is pending in order to ensure there are no tasks
|
||||
allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'pending' });
|
||||
|
@ -146,6 +167,46 @@ module('Acceptance | allocation detail', function(hooks) {
|
|||
assert.notOk(Allocation.hasRescheduleEvents, 'Reschedule Events section exists');
|
||||
});
|
||||
|
||||
test('ports are listed', async function(assert) {
|
||||
const serverNetwork = allocation.allocatedResources.Shared.Networks[0];
|
||||
const allServerPorts = serverNetwork.ReservedPorts.concat(serverNetwork.DynamicPorts);
|
||||
|
||||
allServerPorts.sortBy('Label').forEach((serverPort, index) => {
|
||||
const renderedPort = Allocation.ports[index];
|
||||
|
||||
assert.equal(
|
||||
renderedPort.dynamic,
|
||||
serverNetwork.ReservedPorts.includes(serverPort) ? 'No' : 'Yes'
|
||||
);
|
||||
assert.equal(renderedPort.name, serverPort.Label);
|
||||
assert.equal(renderedPort.address, `${serverNetwork.IP}:${serverPort.Value}`);
|
||||
assert.equal(renderedPort.to, serverPort.To);
|
||||
});
|
||||
});
|
||||
|
||||
test('services are listed', async function(assert) {
|
||||
const taskGroup = server.schema.taskGroups.findBy({ name: allocation.taskGroup });
|
||||
|
||||
assert.equal(Allocation.services.length, taskGroup.services.length);
|
||||
|
||||
taskGroup.services.models.sortBy('name').forEach((serverService, index) => {
|
||||
const renderedService = Allocation.services[index];
|
||||
|
||||
assert.equal(renderedService.name, serverService.name);
|
||||
assert.equal(renderedService.port, serverService.portLabel);
|
||||
assert.equal(renderedService.tags, (serverService.tags || []).join(', '));
|
||||
|
||||
assert.equal(renderedService.connect, serverService.Connect ? 'Yes' : 'No');
|
||||
|
||||
const upstreams = serverService.Connect.SidecarService.Proxy.Upstreams;
|
||||
const serverUpstreamsString = upstreams
|
||||
.map(upstream => `${upstream.DestinationName}:${upstream.LocalBindPort}`)
|
||||
.join(' ');
|
||||
|
||||
assert.equal(renderedService.upstreams, serverUpstreamsString);
|
||||
});
|
||||
});
|
||||
|
||||
test('when the allocation is not found, an error message is shown, but the URL persists', async function(assert) {
|
||||
await Allocation.visit({ id: 'not-a-real-allocation' });
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ module('Acceptance | task detail', function(hooks) {
|
|||
});
|
||||
|
||||
test('/allocation/:id/:task_name should name the task and list high-level task information', async function(assert) {
|
||||
assert.ok(Task.title.includes(task.name), 'Task name');
|
||||
assert.ok(Task.title.text.includes(task.name), 'Task name');
|
||||
assert.ok(Task.state.includes(task.state), 'Task state');
|
||||
|
||||
assert.ok(
|
||||
|
@ -307,3 +307,25 @@ module('Acceptance | task detail (not running)', function(hooks) {
|
|||
assert.equal(Task.resourceEmptyMessage, "Task isn't running", 'Empty message is appropriate');
|
||||
});
|
||||
});
|
||||
|
||||
module('Acceptance | proxy task detail', function(hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function() {
|
||||
server.create('agent');
|
||||
server.create('node');
|
||||
server.create('job', { createAllocations: false });
|
||||
allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' });
|
||||
task = allocation.task_states.models[0];
|
||||
|
||||
task.kind = 'connect-proxy:task';
|
||||
task.save();
|
||||
|
||||
await Task.visit({ id: allocation.id, name: task.name });
|
||||
});
|
||||
|
||||
test('a proxy tag is shown', async function(assert) {
|
||||
assert.ok(Task.title.proxyTag.isPresent);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,6 +44,7 @@ export default create({
|
|||
ports: text('[data-test-ports]'),
|
||||
|
||||
hasUnhealthyDriver: isPresent('[data-test-icon="unhealthy-driver"]'),
|
||||
hasProxyTag: isPresent('[data-test-proxy-tag]'),
|
||||
|
||||
clickLink: clickable('[data-test-name] a'),
|
||||
clickRow: clickable('[data-test-name]'),
|
||||
|
@ -75,6 +76,21 @@ export default create({
|
|||
preempted: isPresent('[data-test-preemptions]'),
|
||||
...allocations('[data-test-preemptions] [data-test-allocation]', 'preemptions'),
|
||||
|
||||
ports: collection('[data-test-allocation-port]', {
|
||||
dynamic: text('[data-test-allocation-port-is-dynamic]'),
|
||||
name: text('[data-test-allocation-port-name]'),
|
||||
address: text('[data-test-allocation-port-address]'),
|
||||
to: text('[data-test-allocation-port-to]'),
|
||||
}),
|
||||
|
||||
services: collection('[data-test-service]', {
|
||||
name: text('[data-test-service-name]'),
|
||||
port: text('[data-test-service-port]'),
|
||||
tags: text('[data-test-service-tags]'),
|
||||
connect: text('[data-test-service-connect]'),
|
||||
upstreams: text('[data-test-service-upstreams]'),
|
||||
}),
|
||||
|
||||
error: {
|
||||
isShown: isPresent('[data-test-error]'),
|
||||
title: text('[data-test-error-title]'),
|
||||
|
|
|
@ -13,7 +13,14 @@ import twoStepButton from 'nomad-ui/tests/pages/components/two-step-button';
|
|||
export default create({
|
||||
visit: visitable('/allocations/:id/:name'),
|
||||
|
||||
title: text('[data-test-title]'),
|
||||
title: {
|
||||
scope: '[data-test-title]',
|
||||
|
||||
proxyTag: {
|
||||
scope: '[data-test-proxy-tag]',
|
||||
},
|
||||
},
|
||||
|
||||
state: text('[data-test-state]'),
|
||||
startedAt: text('[data-test-started-at]'),
|
||||
|
||||
|
|
Loading…
Reference in a new issue