2021-12-28 19:30:38 +00:00
/* eslint-disable qunit/require-expect */
/* eslint-disable qunit/no-conditional-assertions */
2020-06-19 06:20:24 +00:00
import { currentURL , settled } from '@ember/test-helpers' ;
2019-03-13 00:04:16 +00:00
import { module , test } from 'qunit' ;
import { setupApplicationTest } from 'ember-qunit' ;
2019-09-26 18:47:07 +00:00
import { setupMirage } from 'ember-cli-mirage/test-support' ;
2020-07-28 17:59:14 +00:00
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit' ;
2021-03-29 23:16:48 +00:00
import {
formatBytes ,
formatHertz ,
formatScheduledBytes ,
2022-01-20 15:39:02 +00:00
formatScheduledHertz ,
2021-03-29 23:16:48 +00:00
} from 'nomad-ui/utils/units' ;
2018-07-11 17:36:33 +00:00
import TaskGroup from 'nomad-ui/tests/pages/jobs/job/task-group' ;
2020-12-10 17:51:22 +00:00
import Layout from 'nomad-ui/tests/pages/layout' ;
2020-04-01 00:13:44 +00:00
import pageSizeSelect from './behaviors/page-size-select' ;
2017-11-30 23:08:31 +00:00
import moment from 'moment' ;
2017-09-19 14:47:10 +00:00
let job ;
let taskGroup ;
let tasks ;
let allocations ;
2020-06-19 06:20:24 +00:00
let managementToken ;
2017-09-19 14:47:10 +00:00
const sum = ( total , n ) => total + n ;
2022-01-20 15:39:02 +00:00
module ( 'Acceptance | task group detail' , function ( hooks ) {
2019-03-13 00:04:16 +00:00
setupApplicationTest ( hooks ) ;
2019-03-13 01:09:19 +00:00
setupMirage ( hooks ) ;
2019-03-13 00:04:16 +00:00
2022-01-20 15:39:02 +00:00
hooks . beforeEach ( async function ( ) {
2017-09-26 01:13:58 +00:00
server . create ( 'agent' ) ;
2017-09-19 14:47:10 +00:00
server . create ( 'node' , 'forceIPv4' ) ;
job = server . create ( 'job' , {
groupsCount : 2 ,
2022-01-20 15:39:02 +00:00
createAllocations : false ,
2017-09-19 14:47:10 +00:00
} ) ;
const taskGroups = server . db . taskGroups . where ( { jobId : job . id } ) ;
taskGroup = taskGroups [ 0 ] ;
2022-01-20 15:39:02 +00:00
tasks = taskGroup . taskIds . map ( ( id ) => server . db . tasks . find ( id ) ) ;
2017-09-19 14:47:10 +00:00
server . create ( 'node' , 'forceIPv4' ) ;
allocations = server . createList ( 'allocation' , 2 , {
jobId : job . id ,
taskGroup : taskGroup . name ,
2022-01-20 15:39:02 +00:00
clientStatus : 'running' ,
2017-09-19 14:47:10 +00:00
} ) ;
// Allocations associated to a different task group on the job to
// assert that they aren't showing up in on this page in error.
server . createList ( 'allocation' , 3 , {
jobId : job . id ,
taskGroup : taskGroups [ 1 ] . name ,
2022-01-20 15:39:02 +00:00
clientStatus : 'running' ,
2017-09-19 14:47:10 +00:00
} ) ;
2017-10-02 19:44:07 +00:00
// Set a static name to make the search test deterministic
2022-01-20 15:39:02 +00:00
server . db . allocations . forEach ( ( alloc ) => {
2017-10-02 19:44:07 +00:00
alloc . name = 'aaaaa' ;
} ) ;
2018-05-04 21:31:04 +00:00
// Mark the first alloc as rescheduled
allocations [ 0 ] . update ( {
2022-01-20 15:39:02 +00:00
nextAllocation : allocations [ 1 ] . id ,
2018-05-04 21:31:04 +00:00
} ) ;
allocations [ 1 ] . update ( {
2022-01-20 15:39:02 +00:00
previousAllocation : allocations [ 0 ] . id ,
2018-05-04 21:31:04 +00:00
} ) ;
2020-06-19 06:20:24 +00:00
managementToken = server . create ( 'token' ) ;
2020-04-01 00:13:44 +00:00
window . localStorage . clear ( ) ;
2019-03-13 00:04:16 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2022-01-20 15:39:02 +00:00
test ( 'it passes an accessibility audit' , async function ( assert ) {
2020-07-28 17:59:14 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2020-08-25 15:56:02 +00:00
await a11yAudit ( assert ) ;
2020-07-28 17:59:14 +00:00
} ) ;
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group should list high-level metrics for the allocation' , async function ( assert ) {
2020-10-12 22:26:54 +00:00
const totalCPU = tasks . mapBy ( 'resources.CPU' ) . reduce ( sum , 0 ) ;
const totalMemory = tasks . mapBy ( 'resources.MemoryMB' ) . reduce ( sum , 0 ) ;
2021-07-14 20:27:24 +00:00
const totalMemoryMax = tasks
2022-01-20 15:39:02 +00:00
. map ( ( t ) => t . resources . MemoryMaxMB || t . resources . MemoryMB )
2021-07-14 20:27:24 +00:00
. reduce ( sum , 0 ) ;
2019-03-13 00:04:16 +00:00
const totalDisk = taskGroup . ephemeralDisk . SizeMB ;
2017-09-19 14:47:10 +00:00
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2019-03-13 00:04:16 +00:00
assert . equal ( TaskGroup . tasksCount , ` # Tasks ${ tasks . length } ` , '# Tasks' ) ;
assert . equal (
TaskGroup . cpu ,
2021-03-29 23:16:48 +00:00
` Reserved CPU ${ formatScheduledHertz ( totalCPU , 'MHz' ) } ` ,
2019-03-13 00:04:16 +00:00
'Aggregated CPU reservation for all tasks'
) ;
2021-04-28 20:38:14 +00:00
let totalMemoryMaxAddendum = '' ;
if ( totalMemoryMax > totalMemory ) {
2021-12-28 16:08:12 +00:00
totalMemoryMaxAddendum = ` ( ${ formatScheduledBytes (
totalMemoryMax ,
'MiB'
) } Max ) ` ;
2021-04-28 20:38:14 +00:00
}
2019-03-13 00:04:16 +00:00
assert . equal (
TaskGroup . mem ,
2021-12-28 16:08:12 +00:00
` Reserved Memory ${ formatScheduledBytes (
totalMemory ,
'MiB'
) } $ { totalMemoryMaxAddendum } ` ,
2019-03-13 00:04:16 +00:00
'Aggregated Memory reservation for all tasks'
) ;
assert . equal (
TaskGroup . disk ,
2021-03-29 23:16:48 +00:00
` Reserved Disk ${ formatScheduledBytes ( totalDisk , 'MiB' ) } ` ,
2019-03-13 00:04:16 +00:00
'Aggregated Disk reservation for all tasks'
) ;
2019-07-17 20:02:58 +00:00
2021-12-28 16:08:12 +00:00
assert . equal (
document . title ,
` Task group ${ taskGroup . name } - Job ${ job . name } - Nomad `
) ;
2019-03-13 00:04:16 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group should have breadcrumbs for job and jobs' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
Layout . breadcrumbFor ( 'jobs.index' ) . text ,
'Jobs' ,
'First breadcrumb says jobs'
) ;
2019-03-13 00:04:16 +00:00
assert . equal (
2020-12-10 17:51:22 +00:00
Layout . breadcrumbFor ( 'jobs.job.index' ) . text ,
2021-12-16 15:28:46 +00:00
` Job ${ job . name } ` ,
2019-03-13 00:04:16 +00:00
'Second breadcrumb says the job name'
) ;
assert . equal (
2020-12-10 17:51:22 +00:00
Layout . breadcrumbFor ( 'jobs.job.task-group' ) . text ,
2021-12-16 15:28:46 +00:00
` Task Group ${ taskGroup . name } ` ,
2019-03-13 00:04:16 +00:00
'Third breadcrumb says the job name'
) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group first breadcrumb should link to jobs' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2020-12-10 17:51:22 +00:00
await Layout . breadcrumbFor ( 'jobs.index' ) . visit ( ) ;
2017-09-19 14:47:10 +00:00
assert . equal ( currentURL ( ) , '/jobs' , 'First breadcrumb links back to jobs' ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group second breadcrumb should link to the job for the task group' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2020-12-10 17:51:22 +00:00
await Layout . breadcrumbFor ( 'jobs.job.index' ) . visit ( ) ;
2017-09-19 14:47:10 +00:00
assert . equal (
currentURL ( ) ,
` /jobs/ ${ job . id } ` ,
'Second breadcrumb links back to the job for the task group'
) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'when the user has a client token that has a namespace with a policy to run and scale a job the autoscaler options should be available' , async function ( assert ) {
2021-07-14 20:27:24 +00:00
window . localStorage . clear ( ) ;
const SCALE _AND _WRITE _NAMESPACE = 'scale-and-write-namespace' ;
const READ _ONLY _NAMESPACE = 'read-only-namespace' ;
const clientToken = server . create ( 'token' ) ;
server . create ( 'namespace' , { id : SCALE _AND _WRITE _NAMESPACE } ) ;
2021-12-28 16:08:12 +00:00
const secondNamespace = server . create ( 'namespace' , {
2022-01-20 15:39:02 +00:00
id : READ _ONLY _NAMESPACE ,
2021-12-28 16:08:12 +00:00
} ) ;
2021-07-14 20:27:24 +00:00
job = server . create ( 'job' , {
groupCount : 0 ,
createAllocations : false ,
shallow : true ,
noActiveDeployment : true ,
2022-01-20 15:39:02 +00:00
namespaceId : SCALE _AND _WRITE _NAMESPACE ,
2021-07-14 20:27:24 +00:00
} ) ;
const scalingGroup = server . create ( 'task-group' , {
job ,
name : 'scaling' ,
count : 1 ,
shallow : true ,
2022-01-20 15:39:02 +00:00
withScaling : true ,
2021-07-14 20:27:24 +00:00
} ) ;
job . update ( { taskGroupIds : [ scalingGroup . id ] } ) ;
const job2 = server . create ( 'job' , {
groupCount : 0 ,
createAllocations : false ,
shallow : true ,
noActiveDeployment : true ,
2022-01-20 15:39:02 +00:00
namespaceId : READ _ONLY _NAMESPACE ,
2021-07-14 20:27:24 +00:00
} ) ;
const scalingGroup2 = server . create ( 'task-group' , {
job : job2 ,
name : 'scaling' ,
count : 1 ,
shallow : true ,
2022-01-20 15:39:02 +00:00
withScaling : true ,
2021-07-14 20:27:24 +00:00
} ) ;
job2 . update ( { taskGroupIds : [ scalingGroup2 . id ] } ) ;
const policy = server . create ( 'policy' , {
id : 'something' ,
name : 'something' ,
rulesJSON : {
Namespaces : [
{
Name : SCALE _AND _WRITE _NAMESPACE ,
2022-01-20 15:39:02 +00:00
Capabilities : [ 'scale-job' , 'submit-job' , 'read-job' , 'list-jobs' ] ,
2021-07-14 20:27:24 +00:00
} ,
{
Name : READ _ONLY _NAMESPACE ,
2022-01-20 15:39:02 +00:00
Capabilities : [ 'list-jobs' , 'read-job' ] ,
} ,
] ,
} ,
2021-07-14 20:27:24 +00:00
} ) ;
clientToken . policyIds = [ policy . id ] ;
clientToken . save ( ) ;
window . localStorage . nomadTokenSecret = clientToken . secretId ;
await TaskGroup . visit ( {
id : job . id ,
name : scalingGroup . name ,
2022-01-20 15:39:02 +00:00
namespace : SCALE _AND _WRITE _NAMESPACE ,
2021-07-14 20:27:24 +00:00
} ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
currentURL ( ) ,
` /jobs/ ${ job . id } /scaling?namespace= ${ SCALE _AND _WRITE _NAMESPACE } `
) ;
2021-07-14 20:27:24 +00:00
assert . notOk ( TaskGroup . countStepper . increment . isDisabled ) ;
await TaskGroup . visit ( {
id : job2 . id ,
name : scalingGroup2 . name ,
2022-01-20 15:39:02 +00:00
namespace : secondNamespace . name ,
2021-07-14 20:27:24 +00:00
} ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
currentURL ( ) ,
` /jobs/ ${ job2 . id } /scaling?namespace= ${ READ _ONLY _NAMESPACE } `
) ;
2021-07-14 20:27:24 +00:00
assert . ok ( TaskGroup . countStepper . increment . isDisabled ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group should list one page of allocations for the task group' , async function ( assert ) {
2019-03-13 00:04:16 +00:00
server . createList ( 'allocation' , TaskGroup . pageSize , {
jobId : job . id ,
taskGroup : taskGroup . name ,
2022-01-20 15:39:02 +00:00
clientStatus : 'running' ,
2019-03-13 00:04:16 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2019-03-14 06:44:53 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2017-09-19 14:47:10 +00:00
assert . ok (
2021-12-28 16:08:12 +00:00
server . db . allocations . where ( { jobId : job . id } ) . length >
TaskGroup . pageSize ,
2017-09-19 14:47:10 +00:00
'There are enough allocations to invoke pagination'
) ;
assert . equal (
2018-07-11 17:36:33 +00:00
TaskGroup . allocations . length ,
TaskGroup . pageSize ,
2017-09-19 14:47:10 +00:00
'All allocations for the task group'
) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'each allocation should show basic information about the allocation' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2019-03-13 00:04:16 +00:00
const allocation = allocations . sortBy ( 'modifyIndex' ) . reverse ( ) [ 0 ] ;
const allocationRow = TaskGroup . allocations . objectAt ( 0 ) ;
2017-09-19 14:47:10 +00:00
2021-12-28 16:08:12 +00:00
assert . equal (
allocationRow . shortId ,
allocation . id . split ( '-' ) [ 0 ] ,
'Allocation short id'
) ;
2018-07-19 20:30:08 +00:00
assert . equal (
allocationRow . createTime ,
2019-02-01 17:19:14 +00:00
moment ( allocation . createTime / 1000000 ) . format ( 'MMM DD HH:mm:ss ZZ' ) ,
2018-07-19 20:30:08 +00:00
'Allocation create time'
) ;
2017-10-18 02:19:02 +00:00
assert . equal (
2018-07-11 17:36:33 +00:00
allocationRow . modifyTime ,
2018-07-19 20:30:08 +00:00
moment ( allocation . modifyTime / 1000000 ) . fromNow ( ) ,
2017-11-30 23:08:31 +00:00
'Allocation modify time'
2017-10-18 02:19:02 +00:00
) ;
2021-12-28 16:08:12 +00:00
assert . equal (
allocationRow . status ,
allocation . clientStatus ,
'Client status'
) ;
assert . equal (
allocationRow . jobVersion ,
allocation . jobVersion ,
'Job Version'
) ;
2017-10-18 02:19:02 +00:00
assert . equal (
2018-07-11 17:36:33 +00:00
allocationRow . client ,
2017-10-18 02:19:02 +00:00
server . db . nodes . find ( allocation . nodeId ) . id . split ( '-' ) [ 0 ] ,
'Node ID'
) ;
2020-02-12 19:49:28 +00:00
assert . equal (
allocationRow . volume ,
Object . keys ( taskGroup . volumes ) . length ? 'Yes' : '' ,
'Volumes'
) ;
2022-01-13 02:26:02 +00:00
await allocationRow . visitClient ( ) ;
2022-01-24 15:58:28 +00:00
2021-12-28 16:08:12 +00:00
assert . equal (
currentURL ( ) ,
` /clients/ ${ allocation . nodeId } ` ,
'Node links to node page'
) ;
2017-09-26 01:13:58 +00:00
} ) ;
2017-09-19 14:47:10 +00:00
2022-01-20 15:39:02 +00:00
test ( 'each allocation should show stats about the allocation' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2019-03-13 00:04:16 +00:00
const allocation = allocations . sortBy ( 'name' ) [ 0 ] ;
const allocationRow = TaskGroup . allocations . objectAt ( 0 ) ;
const allocStats = server . db . clientAllocationStats . find ( allocation . id ) ;
2022-01-20 15:39:02 +00:00
const tasks = taskGroup . taskIds . map ( ( id ) => server . db . tasks . find ( id ) ) ;
2019-03-13 00:04:16 +00:00
2020-10-12 22:26:54 +00:00
const cpuUsed = tasks . reduce ( ( sum , task ) => sum + task . resources . CPU , 0 ) ;
2021-12-28 16:08:12 +00:00
const memoryUsed = tasks . reduce (
( sum , task ) => sum + task . resources . MemoryMB ,
0
) ;
2019-03-13 00:04:16 +00:00
assert . equal (
allocationRow . cpu ,
Math . floor ( allocStats . resourceUsage . CpuStats . TotalTicks ) / cpuUsed ,
'CPU %'
) ;
2021-12-28 16:08:12 +00:00
const roundedTicks = Math . floor (
allocStats . resourceUsage . CpuStats . TotalTicks
) ;
2019-03-13 00:04:16 +00:00
assert . equal (
allocationRow . cpuTooltip ,
2021-03-29 23:16:48 +00:00
` ${ formatHertz ( roundedTicks , 'MHz' ) } / ${ formatHertz ( cpuUsed , 'MHz' ) } ` ,
2019-03-13 00:04:16 +00:00
'Detailed CPU information is in a tooltip'
) ;
assert . equal (
allocationRow . mem ,
allocStats . resourceUsage . MemoryStats . RSS / 1024 / 1024 / memoryUsed ,
'Memory used'
) ;
assert . equal (
allocationRow . memTooltip ,
2021-03-27 00:02:54 +00:00
` ${ formatBytes ( allocStats . resourceUsage . MemoryStats . RSS ) } / ${ formatBytes (
memoryUsed ,
'MiB'
) } ` ,
2019-03-13 00:04:16 +00:00
'Detailed memory information is in a tooltip'
) ;
} ) ;
2017-10-02 19:44:07 +00:00
2022-01-20 15:39:02 +00:00
test ( 'when the allocation search has no matches, there is an empty message' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2019-03-14 06:44:53 +00:00
await TaskGroup . search ( 'zzzzzz' ) ;
2017-10-02 19:44:07 +00:00
2018-07-11 17:36:33 +00:00
assert . ok ( TaskGroup . isEmpty , 'Empty state is shown' ) ;
assert . equal (
TaskGroup . emptyState . headline ,
'No Matches' ,
'Empty state has an appropriate message'
) ;
2017-10-02 19:44:07 +00:00
} ) ;
2018-05-04 21:31:04 +00:00
2022-01-20 15:39:02 +00:00
test ( 'when the allocation has reschedule events, the allocation row is denoted with an icon' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2019-03-13 00:04:16 +00:00
const rescheduleRow = TaskGroup . allocationFor ( allocations [ 0 ] . id ) ;
const normalRow = TaskGroup . allocationFor ( allocations [ 1 ] . id ) ;
2018-05-04 21:31:04 +00:00
2021-12-28 16:08:12 +00:00
assert . ok (
rescheduleRow . rescheduled ,
'Reschedule row has a reschedule icon'
) ;
2019-03-13 00:04:16 +00:00
assert . notOk ( normalRow . rescheduled , 'Normal row has no reschedule icon' ) ;
} ) ;
2018-11-03 01:24:33 +00:00
2022-01-20 15:39:02 +00:00
test ( '/jobs/:id/:task-group should present task lifecycles' , async function ( assert ) {
2020-04-30 13:15:19 +00:00
job = server . create ( 'job' , {
groupsCount : 2 ,
2022-01-20 15:39:02 +00:00
groupTaskCount : 3 ,
2020-04-30 13:15:19 +00:00
} ) ;
const taskGroups = server . db . taskGroups . where ( { jobId : job . id } ) ;
taskGroup = taskGroups [ 0 ] ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
assert . ok ( TaskGroup . lifecycleChart . isPresent ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
TaskGroup . lifecycleChart . title ,
'Task Lifecycle Configuration'
) ;
2020-04-30 13:15:19 +00:00
2022-01-20 15:39:02 +00:00
tasks = taskGroup . taskIds . map ( ( id ) => server . db . tasks . find ( id ) ) ;
2020-04-30 13:15:19 +00:00
const taskNames = tasks . mapBy ( 'name' ) ;
// This is thoroughly tested in allocation detail tests, so this mostly checks what’ s different
assert . equal ( TaskGroup . lifecycleChart . tasks . length , 3 ) ;
2022-01-20 15:39:02 +00:00
TaskGroup . lifecycleChart . tasks . forEach ( ( Task ) => {
2020-04-30 13:15:19 +00:00
assert . ok ( taskNames . includes ( Task . name ) ) ;
assert . notOk ( Task . isActive ) ;
assert . notOk ( Task . isFinished ) ;
} ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'when the task group depends on volumes, the volumes table is shown' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2020-02-14 00:44:41 +00:00
assert . ok ( TaskGroup . hasVolumes ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
TaskGroup . volumes . length ,
Object . keys ( taskGroup . volumes ) . length
) ;
2020-02-14 00:44:41 +00:00
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'when the task group does not depend on volumes, the volumes table is not shown' , async function ( assert ) {
2020-02-14 00:44:41 +00:00
job = server . create ( 'job' , { noHostVolumes : true , shallow : true } ) ;
taskGroup = server . db . taskGroups . where ( { jobId : job . id } ) [ 0 ] ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
assert . notOk ( TaskGroup . hasVolumes ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'each row in the volumes table lists information about the volume' , async function ( assert ) {
2020-04-01 00:13:44 +00:00
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2022-01-20 15:39:02 +00:00
TaskGroup . volumes [ 0 ] . as ( ( volumeRow ) => {
2020-02-14 00:44:41 +00:00
const volume = taskGroup . volumes [ volumeRow . name ] ;
assert . equal ( volumeRow . name , volume . Name ) ;
assert . equal ( volumeRow . type , volume . Type ) ;
assert . equal ( volumeRow . source , volume . Source ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
volumeRow . permissions ,
volume . ReadOnly ? 'Read' : 'Read/Write'
) ;
2020-02-14 00:44:41 +00:00
} ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'the count stepper sends the appropriate POST request' , async function ( assert ) {
2020-06-19 06:20:24 +00:00
window . localStorage . nomadTokenSecret = managementToken . secretId ;
job = server . create ( 'job' , {
groupCount : 0 ,
createAllocations : false ,
shallow : true ,
2022-01-20 15:39:02 +00:00
noActiveDeployment : true ,
2020-06-19 06:20:24 +00:00
} ) ;
const scalingGroup = server . create ( 'task-group' , {
job ,
name : 'scaling' ,
count : 1 ,
shallow : true ,
2022-01-20 15:39:02 +00:00
withScaling : true ,
2020-06-19 06:20:24 +00:00
} ) ;
job . update ( { taskGroupIds : [ scalingGroup . id ] } ) ;
await TaskGroup . visit ( { id : job . id , name : scalingGroup . name } ) ;
await TaskGroup . countStepper . increment . click ( ) ;
await settled ( ) ;
2020-07-28 04:27:03 +00:00
const scaleRequest = server . pretender . handledRequests . find (
2022-01-20 15:39:02 +00:00
( req ) => req . method === 'POST' && req . url . endsWith ( '/scale' )
2020-07-28 04:27:03 +00:00
) ;
2020-06-19 06:20:24 +00:00
const requestBody = JSON . parse ( scaleRequest . requestBody ) ;
assert . equal ( requestBody . Target . Group , scalingGroup . name ) ;
assert . equal ( requestBody . Count , scalingGroup . count + 1 ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'the count stepper is disabled when a deployment is running' , async function ( assert ) {
2020-06-19 06:20:24 +00:00
window . localStorage . nomadTokenSecret = managementToken . secretId ;
job = server . create ( 'job' , {
groupCount : 0 ,
createAllocations : false ,
shallow : true ,
2022-01-20 15:39:02 +00:00
activeDeployment : true ,
2020-06-19 06:20:24 +00:00
} ) ;
const scalingGroup = server . create ( 'task-group' , {
job ,
name : 'scaling' ,
count : 1 ,
shallow : true ,
2022-01-20 15:39:02 +00:00
withScaling : true ,
2020-06-19 06:20:24 +00:00
} ) ;
job . update ( { taskGroupIds : [ scalingGroup . id ] } ) ;
await TaskGroup . visit ( { id : job . id , name : scalingGroup . name } ) ;
assert . ok ( TaskGroup . countStepper . input . isDisabled ) ;
assert . ok ( TaskGroup . countStepper . increment . isDisabled ) ;
assert . ok ( TaskGroup . countStepper . decrement . isDisabled ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'when the job for the task group is not found, an error message is shown, but the URL persists' , async function ( assert ) {
2021-12-28 16:08:12 +00:00
await TaskGroup . visit ( {
id : 'not-a-real-job' ,
2022-01-20 15:39:02 +00:00
name : 'not-a-real-task-group' ,
2021-12-28 16:08:12 +00:00
} ) ;
2018-11-03 01:24:33 +00:00
assert . equal (
2020-01-20 20:57:01 +00:00
server . pretender . handledRequests
2022-01-20 15:39:02 +00:00
. filter ( ( request ) => ! request . url . includes ( 'policy' ) )
2020-01-20 20:57:01 +00:00
. findBy ( 'status' , 404 ) . url ,
2018-11-03 01:24:33 +00:00
'/v1/job/not-a-real-job' ,
'A request to the nonexistent job is made'
) ;
2021-12-28 16:08:12 +00:00
assert . equal (
currentURL ( ) ,
'/jobs/not-a-real-job/not-a-real-task-group' ,
'The URL persists'
) ;
2018-11-03 01:24:33 +00:00
assert . ok ( TaskGroup . error . isPresent , 'Error message is shown' ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
TaskGroup . error . title ,
'Not Found' ,
'Error message is for 404'
) ;
2018-11-03 01:24:33 +00:00
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'when the task group is not found on the job, an error message is shown, but the URL persists' , async function ( assert ) {
2019-03-14 06:44:53 +00:00
await TaskGroup . visit ( { id : job . id , name : 'not-a-real-task-group' } ) ;
2018-11-03 01:24:33 +00:00
assert . ok (
server . pretender . handledRequests
. filterBy ( 'status' , 200 )
. mapBy ( 'url' )
. includes ( ` /v1/job/ ${ job . id } ` ) ,
'A request to the job is made and succeeds'
) ;
2021-12-28 16:08:12 +00:00
assert . equal (
currentURL ( ) ,
` /jobs/ ${ job . id } /not-a-real-task-group ` ,
'The URL persists'
) ;
2018-11-03 01:24:33 +00:00
assert . ok ( TaskGroup . error . isPresent , 'Error message is shown' ) ;
2021-12-28 16:08:12 +00:00
assert . equal (
TaskGroup . error . title ,
'Not Found' ,
'Error message is for 404'
) ;
2018-11-03 01:24:33 +00:00
} ) ;
2020-04-01 00:13:44 +00:00
pageSizeSelect ( {
resourceName : 'allocation' ,
pageObject : TaskGroup ,
pageObjectList : TaskGroup . allocations ,
async setup ( ) {
server . createList ( 'allocation' , TaskGroup . pageSize , {
jobId : job . id ,
taskGroup : taskGroup . name ,
2022-01-20 15:39:02 +00:00
clientStatus : 'running' ,
2020-04-01 00:13:44 +00:00
} ) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2022-01-20 15:39:02 +00:00
} ,
2020-04-01 00:13:44 +00:00
} ) ;
2020-07-28 06:18:10 +00:00
2022-01-20 15:39:02 +00:00
test ( 'when a task group has no scaling events, there is no recent scaling events section' , async function ( assert ) {
2021-12-28 14:45:20 +00:00
const taskGroupScale = job . jobScale . taskGroupScales . models . find (
2022-01-20 15:39:02 +00:00
( m ) => m . name === taskGroup . name
2021-12-28 14:45:20 +00:00
) ;
2020-07-28 06:18:10 +00:00
taskGroupScale . update ( { events : [ ] } ) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
assert . notOk ( TaskGroup . hasScaleEvents ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( 'the recent scaling events section shows all recent scaling events in reverse chronological order' , async function ( assert ) {
2021-12-28 14:45:20 +00:00
const taskGroupScale = job . jobScale . taskGroupScales . models . find (
2022-01-20 15:39:02 +00:00
( m ) => m . name === taskGroup . name
2021-12-28 14:45:20 +00:00
) ;
2020-08-05 05:54:26 +00:00
taskGroupScale . update ( {
events : [
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { count : 3 , error : false } ) ,
2022-01-20 15:39:02 +00:00
server . create ( 'scale-event' , { count : 1 , error : false } ) ,
] ,
2020-08-05 05:54:26 +00:00
} ) ;
2020-07-28 06:18:10 +00:00
const scaleEvents = taskGroupScale . events . models . sortBy ( 'time' ) . reverse ( ) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
assert . ok ( TaskGroup . hasScaleEvents ) ;
2020-08-05 05:54:26 +00:00
assert . notOk ( TaskGroup . hasScalingTimeline ) ;
2020-07-28 06:18:10 +00:00
scaleEvents . forEach ( ( scaleEvent , idx ) => {
const ScaleEvent = TaskGroup . scaleEvents [ idx ] ;
2021-12-28 16:08:12 +00:00
assert . equal (
ScaleEvent . time ,
moment ( scaleEvent . time / 1000000 ) . format ( 'MMM DD HH:mm:ss ZZ' )
) ;
2020-07-28 06:18:10 +00:00
assert . equal ( ScaleEvent . message , scaleEvent . message ) ;
2020-08-05 05:54:26 +00:00
if ( scaleEvent . count != null ) {
assert . equal ( ScaleEvent . count , scaleEvent . count ) ;
}
2020-07-28 06:18:10 +00:00
if ( scaleEvent . error ) {
assert . ok ( ScaleEvent . error ) ;
}
if ( Object . keys ( scaleEvent . meta ) . length ) {
assert . ok ( ScaleEvent . isToggleable ) ;
} else {
assert . notOk ( ScaleEvent . isToggleable ) ;
}
} ) ;
} ) ;
2020-08-05 05:54:26 +00:00
2022-01-20 15:39:02 +00:00
test ( 'when a task group has at least two count scaling events and the count scaling events outnumber the non-count scaling events, a timeline is shown in addition to the accordion' , async function ( assert ) {
2021-12-28 14:45:20 +00:00
const taskGroupScale = job . jobScale . taskGroupScales . models . find (
2022-01-20 15:39:02 +00:00
( m ) => m . name === taskGroup . name
2021-12-28 14:45:20 +00:00
) ;
2020-08-05 05:54:26 +00:00
taskGroupScale . update ( {
events : [
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { error : true } ) ,
server . create ( 'scale-event' , { count : 7 , error : false } ) ,
server . create ( 'scale-event' , { count : 10 , error : false } ) ,
server . create ( 'scale-event' , { count : 2 , error : false } ) ,
server . create ( 'scale-event' , { count : 3 , error : false } ) ,
server . create ( 'scale-event' , { count : 2 , error : false } ) ,
server . create ( 'scale-event' , { count : 9 , error : false } ) ,
2022-01-20 15:39:02 +00:00
server . create ( 'scale-event' , { count : 1 , error : false } ) ,
] ,
2020-08-05 05:54:26 +00:00
} ) ;
const scaleEvents = taskGroupScale . events . models . sortBy ( 'time' ) . reverse ( ) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
2020-08-22 01:44:13 +00:00
assert . ok ( TaskGroup . hasScaleEvents ) ;
2020-08-05 05:54:26 +00:00
assert . ok ( TaskGroup . hasScalingTimeline ) ;
assert . equal (
TaskGroup . scalingAnnotations . length ,
2022-01-20 15:39:02 +00:00
scaleEvents . filter ( ( ev ) => ev . count == null ) . length
2020-08-05 05:54:26 +00:00
) ;
} ) ;
2021-12-17 23:45:31 +00:00
testFacet ( 'Status' , {
facet : TaskGroup . facets . status ,
paramName : 'status' ,
expectedOptions : [ 'Pending' , 'Running' , 'Complete' , 'Failed' , 'Lost' ] ,
async beforeEach ( ) {
2022-01-20 15:39:02 +00:00
[ 'pending' , 'running' , 'complete' , 'failed' , 'lost' ] . forEach ( ( s ) => {
2021-12-17 23:45:31 +00:00
server . createList ( 'allocation' , 5 , { clientStatus : s } ) ;
} ) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
} ,
filter : ( alloc , selection ) =>
alloc . jobId == job . id &&
alloc . taskGroup == taskGroup . name &&
2022-01-20 15:39:02 +00:00
selection . includes ( alloc . clientStatus ) ,
2021-12-17 23:45:31 +00:00
} ) ;
testFacet ( 'Client' , {
facet : TaskGroup . facets . client ,
paramName : 'client' ,
expectedOptions ( allocs ) {
return Array . from (
new Set (
allocs
2021-12-28 16:08:12 +00:00
. filter (
2022-01-20 15:39:02 +00:00
( alloc ) =>
2021-12-28 16:08:12 +00:00
alloc . jobId == job . id && alloc . taskGroup == taskGroup . name
)
2021-12-17 23:45:31 +00:00
. mapBy ( 'nodeId' )
2022-01-20 15:39:02 +00:00
. map ( ( id ) => id . split ( '-' ) [ 0 ] )
2021-12-17 23:45:31 +00:00
)
) . sort ( ) ;
} ,
async beforeEach ( ) {
const nodes = server . createList ( 'node' , 3 , 'forceIPv4' ) ;
2022-01-20 15:39:02 +00:00
nodes . forEach ( ( node ) =>
2021-12-17 23:45:31 +00:00
server . createList ( 'allocation' , 5 , {
nodeId : node . id ,
jobId : job . id ,
2022-01-20 15:39:02 +00:00
taskGroup : taskGroup . name ,
2021-12-17 23:45:31 +00:00
} )
) ;
await TaskGroup . visit ( { id : job . id , name : taskGroup . name } ) ;
} ,
filter : ( alloc , selection ) =>
alloc . jobId == job . id &&
alloc . taskGroup == taskGroup . name &&
2022-01-20 15:39:02 +00:00
selection . includes ( alloc . nodeId . split ( '-' ) [ 0 ] ) ,
2021-12-17 23:45:31 +00:00
} ) ;
2018-11-03 01:24:33 +00:00
} ) ;
2021-12-17 23:45:31 +00:00
2021-12-28 16:08:12 +00:00
function testFacet (
label ,
{ facet , paramName , beforeEach , filter , expectedOptions }
) {
2022-01-20 15:39:02 +00:00
test ( ` facet ${ label } | the ${ label } facet has the correct options ` , async function ( assert ) {
2021-12-17 23:45:31 +00:00
await beforeEach ( ) ;
await facet . toggle ( ) ;
let expectation ;
if ( typeof expectedOptions === 'function' ) {
expectation = expectedOptions ( server . db . allocations ) ;
} else {
expectation = expectedOptions ;
}
assert . deepEqual (
2022-01-20 15:39:02 +00:00
facet . options . map ( ( option ) => option . label . trim ( ) ) ,
2021-12-17 23:45:31 +00:00
expectation ,
'Options for facet are as expected'
) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( ` facet ${ label } | the ${ label } facet filters the allocations list by ${ label } ` , async function ( assert ) {
2021-12-17 23:45:31 +00:00
let option ;
await beforeEach ( ) ;
await facet . toggle ( ) ;
option = facet . options . objectAt ( 0 ) ;
await option . toggle ( ) ;
const selection = [ option . key ] ;
const expectedAllocs = server . db . allocations
2022-01-20 15:39:02 +00:00
. filter ( ( alloc ) => filter ( alloc , selection ) )
2021-12-17 23:45:31 +00:00
. sortBy ( 'modifyIndex' )
. reverse ( ) ;
TaskGroup . allocations . forEach ( ( alloc , index ) => {
assert . equal (
alloc . id ,
expectedAllocs [ index ] . id ,
` Allocation at ${ index } is ${ expectedAllocs [ index ] . id } `
) ;
} ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( ` facet ${ label } | selecting multiple options in the ${ label } facet results in a broader search ` , async function ( assert ) {
2021-12-17 23:45:31 +00:00
const selection = [ ] ;
await beforeEach ( ) ;
await facet . toggle ( ) ;
const option1 = facet . options . objectAt ( 0 ) ;
const option2 = facet . options . objectAt ( 1 ) ;
await option1 . toggle ( ) ;
selection . push ( option1 . key ) ;
await option2 . toggle ( ) ;
selection . push ( option2 . key ) ;
const expectedAllocs = server . db . allocations
2022-01-20 15:39:02 +00:00
. filter ( ( alloc ) => filter ( alloc , selection ) )
2021-12-17 23:45:31 +00:00
. sortBy ( 'modifyIndex' )
. reverse ( ) ;
TaskGroup . allocations . forEach ( ( alloc , index ) => {
assert . equal (
alloc . id ,
expectedAllocs [ index ] . id ,
` Allocation at ${ index } is ${ expectedAllocs [ index ] . id } `
) ;
} ) ;
} ) ;
2022-01-20 15:39:02 +00:00
test ( ` facet ${ label } | selecting options in the ${ label } facet updates the ${ paramName } query param ` , async function ( assert ) {
2021-12-17 23:45:31 +00:00
const selection = [ ] ;
await beforeEach ( ) ;
await facet . toggle ( ) ;
const option1 = facet . options . objectAt ( 0 ) ;
const option2 = facet . options . objectAt ( 1 ) ;
await option1 . toggle ( ) ;
selection . push ( option1 . key ) ;
await option2 . toggle ( ) ;
selection . push ( option2 . key ) ;
assert . equal (
currentURL ( ) ,
` /jobs/ ${ job . id } / ${ taskGroup . name } ? ${ paramName } = ${ encodeURIComponent (
JSON . stringify ( selection )
) } ` ,
'URL has the correct query param key and value'
) ;
} ) ;
}