Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui, deployments] Mirage Actively Deploying Job and Deployment Integration Tests #16888

3 changes: 3 additions & 0 deletions ui/app/components/job-status/allocation-status-block.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
@label="View allocation"
>
{{#if (and (eq @status "running") (not @steady))}}
{{#if (eq @canary "canary")}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Where are you injecting this argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is injected in the nested each-in blocks of allocation-status-row.hbs:

<JobStatus::AllocationStatusBlock
@status={{status}}
@health={{health}}
@canary={{canary}}
@steady={{@steady}}
@count={{allocsByCanary.length}}
@width={{compute (action this.calcPerc) allocsByCanary.length}}
@allocs={{allocsByCanary}} />

<span class="alloc-canary-indicator" />
{{/if}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side-effect: we weren't showing the canary indicator for running, non-steady, ungrouped allocs. Now we are.

<span class="alloc-health-indicator">
{{#if (eq @health "healthy")}}
<FlightIcon @name="check" @color="white" />
Expand Down
16 changes: 10 additions & 6 deletions ui/app/components/job-status/panel/deploying.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
<div class="boxed-section-body">
<div class="deployment-allocations">
{{#if this.oldVersionAllocBlockIDs.length}}
<h4 class="title is-5">Previous Allocations: {{#if this.oldVersionAllocBlocks.running}}{{this.oldRunningHealthyAllocBlocks.length}} running{{/if}}</h4>
<JobStatus::AllocationStatusRow @allocBlocks={{this.oldVersionAllocBlocks}} @steady={{true}} />
<div class="legend-and-summary">
<h4 class="title is-5" data-test-old-allocation-tally>Previous allocations: {{#if this.oldVersionAllocBlocks.running}}{{this.oldRunningHealthyAllocBlocks.length}} running{{/if}}</h4>
<div class="previous-allocations">
<JobStatus::AllocationStatusRow @allocBlocks={{this.oldVersionAllocBlocks}} @steady={{true}} />
</div>
<div class="legend-and-summary" data-test-previous-allocations-legend>
<legend>
<span class="legend-item {{if (eq (get this.oldRunningHealthyAllocBlocks "length") 0) "faded"}}">
<span class="represented-allocation running"></span>
Expand All @@ -53,11 +55,13 @@

{{/if}}

<h4 class="title is-5">New allocations: {{this.newRunningHealthyAllocBlocks.length}}/{{this.totalAllocs}} running and healthy</h4>
<JobStatus::AllocationStatusRow @allocBlocks={{this.newVersionAllocBlocks}} />
<h4 class="title is-5" data-test-new-allocation-tally>New allocations: {{this.newRunningHealthyAllocBlocks.length}}/{{this.totalAllocs}} running and healthy</h4>
<div class="new-allocations">
<JobStatus::AllocationStatusRow @allocBlocks={{this.newVersionAllocBlocks}} />
</div>
</div>

<div class="legend-and-summary">
<div class="legend-and-summary" data-test-new-allocations-legend>

{{!-- Legend by Status, then by Health, then by Canary --}}
<legend>
Expand Down
25 changes: 19 additions & 6 deletions ui/mirage/factories/deployment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import faker from 'nomad-ui/mirage/faker';
import { provide } from '../utils';

const UUIDS = provide(100, faker.random.uuid.bind(faker.random));
const DEPLOYMENT_STATUSES = ['running', 'successful', 'paused', 'failed', 'cancelled'];
const DEPLOYMENT_STATUSES = [
'running',
'successful',
'paused',
'failed',
'cancelled',
];

export default Factory.extend({
id: i => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),
id: (i) => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),

jobId: null,
versionNumber: null,
groupDesiredTotal: null,

status: () => faker.helpers.randomize(DEPLOYMENT_STATUSES),
statusDescription: () => faker.lorem.sentence(),
Expand All @@ -24,14 +31,20 @@ export default Factory.extend({

afterCreate(deployment, server) {
const job = server.db.jobs.find(deployment.jobId);
const groups = job.taskGroupIds.map(id =>
server.create('deployment-task-group-summary', {
const groups = job.taskGroupIds.map((id) => {
let summary = server.create('deployment-task-group-summary', {
deployment,
name: server.db.taskGroups.find(id).name,
desiredCanaries: 1,
promoted: false,
})
);
});
if (deployment.groupDesiredTotal) {
summary.update({
desiredTotal: deployment.groupDesiredTotal,
});
}
return summary;
});

deployment.update({
deploymentTaskGroupSummaryIds: groups.mapBy('id'),
Expand Down
40 changes: 25 additions & 15 deletions ui/mirage/factories/task-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,29 @@ export default Factory.extend({
unknown: 0.25,
lost: 0.1,
};

Array(group.count)

const totalAllocations = group.count;
const allocationsByStatus = {};

Object.entries(statusProbabilities).forEach(([status, prob]) => {
allocationsByStatus[status] = Math.round(totalAllocations * prob);
});

let currentStatusIndex = 0;
const statusKeys = Object.keys(allocationsByStatus);

Array(totalAllocations)
.fill(null)
.forEach((_, i) => {
let rand = faker.random.number({ min: 1, max: 100 }) / 100; // emulate Math.random float precision, but observe Faker random seed

let clientStatus;

Object.entries(statusProbabilities).some(([status, prob]) => {
if (rand < prob) {
clientStatus = status;
return true;
}
rand -= prob;
return false;
});


while (allocationsByStatus[statusKeys[currentStatusIndex]] === 0) {
currentStatusIndex++;
}

clientStatus = statusKeys[currentStatusIndex];
allocationsByStatus[clientStatus]--;

const props = {
jobId: group.job.id,
namespace: group.job.namespace,
Expand All @@ -147,8 +153,12 @@ export default Factory.extend({
? faker.random.number({ min: 1, max: 5 })
: 0,
clientStatus,
deploymentStatus: {
Canary: false,
Healthy: false,
},
};

if (group.withRescheduling) {
server.create('allocation', 'rescheduled', props);
} else {
Expand Down
70 changes: 70 additions & 0 deletions ui/mirage/scenarios/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,76 @@ function smallCluster(server) {
type: 'service',
activeDeployment: true,
});

//#region Active Deployment
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New default (small cluster) job to show off the deployment panel


const activelyDeployingJobGroups = 2;
const activelyDeployingTasksPerGroup = 100;

const activelyDeployingJob = server.create('job', {
createAllocations: true,
groupTaskCount: activelyDeployingTasksPerGroup,
shallow: true,
resourceSpec: Array(activelyDeployingJobGroups).fill(['M: 257, C: 500']),
noDeployments: true, // manually created below
activeDeployment: true,
allocStatusDistribution: {
running: 0.6,
failed: 0.05,
unknown: 0.05,
lost: 0,
complete: 0,
pending: 0.3,
},
name: 'actively-deploying-job',
id: 'actively-deploying-job',
namespaceId: 'default',
type: 'service',
});

server.create('deployment', false, 'active', {
jobId: activelyDeployingJob.id,
groupDesiredTotal: activelyDeployingTasksPerGroup,
versionNumber: 1,
status: 'running',
});
server.createList('allocation', 25, {
jobId: activelyDeployingJob.id,
jobVersion: 0,
clientStatus: 'running',
});

// Manipulate the above job to show a nice distribution of running, canary, etc. allocs
let activelyDeployingJobAllocs = server.schema.allocations
.all()
.filter((a) => a.jobId === activelyDeployingJob.id);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(0, 10)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: false, Canary: true } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(10, 20)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: true } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(20, 65)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: false } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'pending')
.slice(0, 10)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: true } })
);

//#endregion Active Deployment

server.createList('allocFile', 5);
server.create('allocFile', 'dir', { depth: 2 });
server.createList('csi-plugin', 2);
Expand Down
54 changes: 34 additions & 20 deletions ui/tests/acceptance/job-status-panel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,7 @@ module('Acceptance | job status panel', function (hooks) {
});

test('Status Panel groups allocations when they get past a threshold, multiple statuses', async function (assert) {
faker.seed(3);
let groupTaskCount = 51;
let groupTaskCount = 50;

let job = server.create('job', {
status: 'running',
Expand All @@ -274,12 +273,12 @@ module('Acceptance | job status panel', function (hooks) {
await visit(`/jobs/${job.id}`);
assert.dom('.job-status-panel').exists();

// With 51 allocs split across 4 statuses distributed as above, we can expect 25 running, 16 failed, 6 pending, and 4 remaining.
// With 50 allocs split across 4 statuses distributed as above, we can expect 25 running, 16 failed, 6 pending, and 4 remaining.
// At standard test resolution, each status will be ungrouped/grouped as follows:
// 25 running: 9 ungrouped, 16 grouped
// 13 failed: 5 ungrouped, 11 grouped
// 9 pending: 0 ungrouped, 6 grouped
// 4 lost: 0 ungrouped, 4 grouped. Represented as "Unplaced"
// 25 running: 9 ungrouped, 17 grouped
// 15 failed: 5 ungrouped, 10 grouped
// 5 pending: 0 ungrouped, 5 grouped
// 5 lost: 0 ungrouped, 5 grouped. Represented as "Unplaced"

assert
.dom('.ungrouped-allocs .represented-allocation.running')
Expand All @@ -298,7 +297,7 @@ module('Acceptance | job status panel', function (hooks) {

assert
.dom('.ungrouped-allocs .represented-allocation.failed')
.exists({ count: 4 }, '4 failed allocations are represented ungrouped');
.exists({ count: 5 }, '5 failed allocations are represented ungrouped');
assert
.dom('.represented-allocation.rest.failed')
.exists(
Expand All @@ -307,7 +306,7 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.failed')
.hasText(
'+9',
'+10',
'Summary block has the correct number of grouped failed allocs'
);

Expand All @@ -322,7 +321,7 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.pending')
.hasText(
'9',
'5',
'Summary block has the correct number of grouped pending allocs'
);

Expand All @@ -337,7 +336,7 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.unplaced')
.hasText(
'4',
'5',
'Summary block has the correct number of grouped unplaced allocs'
);
await percySnapshot(
Expand All @@ -346,17 +345,17 @@ module('Acceptance | job status panel', function (hooks) {

// Simulate a window resize event; will recompute how many of each ought to be grouped.

// At 1000px, only running allocations have some ungrouped allocs. The rest are all fully grouped.
find('.page-body').style.width = '1000px';
// At 1100px, only running and failed allocations have some ungrouped allocs
find('.page-body').style.width = '1100px';
await triggerEvent(window, 'resize');

await percySnapshot(
'Status Panel groups allocations when they get past a threshold, multiple statuses (1000px)'
'Status Panel groups allocations when they get past a threshold, multiple statuses (1100px)'
);

assert
.dom('.ungrouped-allocs .represented-allocation.running')
.exists({ count: 6 }, '6 running allocations are represented ungrouped');
.exists({ count: 7 }, '7 running allocations are represented ungrouped');
assert
.dom('.represented-allocation.rest.running')
.exists(
Expand All @@ -365,13 +364,13 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.running')
.hasText(
'+19',
'+18',
'Summary block has the correct number of grouped running allocs'
);

assert
.dom('.ungrouped-allocs .represented-allocation.failed')
.doesNotExist('5 failed allocations are represented ungrouped');
.exists({ count: 4 }, '4 failed allocations are represented ungrouped');
assert
.dom('.represented-allocation.rest.failed')
.exists(
Expand All @@ -380,7 +379,7 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.failed')
.hasText(
'13',
'+11',
'Summary block has the correct number of grouped failed allocs'
);

Expand All @@ -394,7 +393,7 @@ module('Acceptance | job status panel', function (hooks) {

assert
.dom('.ungrouped-allocs .represented-allocation.running')
.doesNotExist('6 running allocations are represented ungrouped');
.exists({ count: 4 }, '4 running allocations are represented ungrouped');
assert
.dom('.represented-allocation.rest.running')
.exists(
Expand All @@ -403,9 +402,24 @@ module('Acceptance | job status panel', function (hooks) {
assert
.dom('.represented-allocation.rest.running')
.hasText(
'25',
'+21',
'Summary block has the correct number of grouped running allocs'
);

assert
.dom('.ungrouped-allocs .represented-allocation.failed')
.doesNotExist('no failed allocations are represented ungrouped');
assert
.dom('.represented-allocation.rest.failed')
.exists(
'Failed allocations are numerous enough that a summary block exists'
);
assert
.dom('.represented-allocation.rest.failed')
.hasText(
'15',
'Summary block has the correct number of grouped failed allocs'
);
});

module('deployment history', function () {
Expand Down
Loading