github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/acceptance/allocation-detail-test.js (about)

     1  import { run } from '@ember/runloop';
     2  import { currentURL } from '@ember/test-helpers';
     3  import { assign } from '@ember/polyfills';
     4  import { module, test } from 'qunit';
     5  import { setupApplicationTest } from 'ember-qunit';
     6  import { setupMirage } from 'ember-cli-mirage/test-support';
     7  import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
     8  import Allocation from 'nomad-ui/tests/pages/allocations/detail';
     9  import moment from 'moment';
    10  import isIp from 'is-ip';
    11  
    12  let job;
    13  let node;
    14  let allocation;
    15  
    16  module('Acceptance | allocation detail', function(hooks) {
    17    setupApplicationTest(hooks);
    18    setupMirage(hooks);
    19  
    20    hooks.beforeEach(async function() {
    21      server.create('agent');
    22  
    23      node = server.create('node');
    24      job = server.create('job', {
    25        groupsCount: 1,
    26        withGroupServices: true,
    27        createAllocations: false,
    28      });
    29      allocation = server.create('allocation', 'withTaskWithPorts', {
    30        clientStatus: 'running',
    31      });
    32  
    33      // Make sure the node has an unhealthy driver
    34      node.update({
    35        driver: assign(node.drivers, {
    36          docker: {
    37            detected: true,
    38            healthy: false,
    39          },
    40        }),
    41      });
    42  
    43      // Make sure a task for the allocation depends on the unhealthy driver
    44      server.schema.tasks.first().update({
    45        driver: 'docker',
    46      });
    47  
    48      await Allocation.visit({ id: allocation.id });
    49    });
    50  
    51    test('it passes an accessibility audit', async function(assert) {
    52      await a11yAudit(assert);
    53    });
    54  
    55    test('/allocation/:id should name the allocation and link to the corresponding job and node', async function(assert) {
    56      assert.ok(Allocation.title.includes(allocation.name), 'Allocation name is in the heading');
    57      assert.equal(Allocation.details.job, job.name, 'Job name is in the subheading');
    58      assert.equal(
    59        Allocation.details.client,
    60        node.id.split('-')[0],
    61        'Node short id is in the subheading'
    62      );
    63      assert.ok(Allocation.execButton.isPresent);
    64  
    65      assert.equal(document.title, `Allocation ${allocation.name} - Nomad`);
    66  
    67      await Allocation.details.visitJob();
    68      assert.equal(currentURL(), `/jobs/${job.id}`, 'Job link navigates to the job');
    69  
    70      await Allocation.visit({ id: allocation.id });
    71  
    72      await Allocation.details.visitClient();
    73      assert.equal(currentURL(), `/clients/${node.id}`, 'Client link navigates to the client');
    74    });
    75  
    76    test('/allocation/:id should include resource utilization graphs', async function(assert) {
    77      assert.equal(Allocation.resourceCharts.length, 2, 'Two resource utilization graphs');
    78      assert.equal(Allocation.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU');
    79      assert.equal(Allocation.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory');
    80    });
    81  
    82    test('/allocation/:id should present task lifecycles', async function(assert) {
    83      const job = server.create('job', {
    84        groupsCount: 1,
    85        groupTaskCount: 6,
    86        withGroupServices: true,
    87        createAllocations: false,
    88      });
    89  
    90      const allocation = server.create('allocation', 'withTaskWithPorts', {
    91        clientStatus: 'running',
    92        jobId: job.id,
    93      });
    94  
    95      await Allocation.visit({ id: allocation.id });
    96  
    97      assert.ok(Allocation.lifecycleChart.isPresent);
    98      assert.equal(Allocation.lifecycleChart.title, 'Task Lifecycle Status');
    99      assert.equal(Allocation.lifecycleChart.phases.length, 4);
   100      assert.equal(Allocation.lifecycleChart.tasks.length, 6);
   101  
   102      await Allocation.lifecycleChart.tasks[0].visit();
   103  
   104      const prestartEphemeralTask = server.db.taskStates
   105        .where({ allocationId: allocation.id })
   106        .sortBy('name')
   107        .find(taskState => {
   108          const task = server.db.tasks.findBy({ name: taskState.name });
   109          return task.Lifecycle && task.Lifecycle.Hook === 'prestart' && !task.Lifecycle.Sidecar;
   110        });
   111  
   112      assert.equal(currentURL(), `/allocations/${allocation.id}/${prestartEphemeralTask.name}`);
   113    });
   114  
   115    test('/allocation/:id should list all tasks for the allocation', async function(assert) {
   116      assert.equal(
   117        Allocation.tasks.length,
   118        server.db.taskStates.where({ allocationId: allocation.id }).length,
   119        'Table lists all tasks'
   120      );
   121      assert.notOk(Allocation.isEmpty, 'Task table empty state is not shown');
   122    });
   123  
   124    test('each task row should list high-level information for the task', async function(assert) {
   125      const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0];
   126      const events = server.db.taskEvents.where({ taskStateId: task.id });
   127      const event = events[events.length - 1];
   128  
   129      const taskGroup = server.schema.taskGroups.where({
   130        jobId: allocation.jobId,
   131        name: allocation.taskGroup,
   132      }).models[0];
   133  
   134      const jobTask = taskGroup.tasks.models.find(m => m.name === task.name);
   135      const volumes = jobTask.volumeMounts.map(volume => ({
   136        name: volume.Volume,
   137        source: taskGroup.volumes[volume.Volume].Source,
   138      }));
   139  
   140      Allocation.tasks[0].as(taskRow => {
   141        assert.equal(taskRow.name, task.name, 'Name');
   142        assert.equal(taskRow.state, task.state, 'State');
   143        assert.equal(taskRow.message, event.displayMessage, 'Event Message');
   144        assert.equal(
   145          taskRow.time,
   146          moment(event.time / 1000000).format("MMM DD, 'YY HH:mm:ss ZZ"),
   147          'Event Time'
   148        );
   149  
   150        const volumesText = taskRow.volumes;
   151        volumes.forEach(volume => {
   152          assert.ok(volumesText.includes(volume.name), `Found label ${volume.name}`);
   153          assert.ok(volumesText.includes(volume.source), `Found value ${volume.source}`);
   154        });
   155      });
   156    });
   157  
   158    test('each task row should link to the task detail page', async function(assert) {
   159      const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0];
   160  
   161      await Allocation.tasks.objectAt(0).clickLink();
   162  
   163      assert.equal(
   164        currentURL(),
   165        `/allocations/${allocation.id}/${task.name}`,
   166        'Task name in task row links to task detail'
   167      );
   168  
   169      await Allocation.visit({ id: allocation.id });
   170      await Allocation.tasks.objectAt(0).clickRow();
   171  
   172      assert.equal(
   173        currentURL(),
   174        `/allocations/${allocation.id}/${task.name}`,
   175        'Task row links to task detail'
   176      );
   177    });
   178  
   179    test('tasks with an unhealthy driver have a warning icon', async function(assert) {
   180      assert.ok(Allocation.firstUnhealthyTask().hasUnhealthyDriver, 'Warning is shown');
   181    });
   182  
   183    test('proxy task has a proxy tag', async function(assert) {
   184      // Must create a new job as existing one has loaded and it contains the tasks
   185      job = server.create('job', {
   186        groupsCount: 1,
   187        withGroupServices: true,
   188        createAllocations: false,
   189      });
   190  
   191      allocation = server.create('allocation', 'withTaskWithPorts', {
   192        clientStatus: 'running',
   193        jobId: job.id,
   194      });
   195  
   196      const taskState = allocation.taskStates.models.sortBy('name')[0];
   197      const task = server.schema.tasks.findBy({ name: taskState.name });
   198      task.update('kind', 'connect-proxy:task');
   199      task.save();
   200  
   201      await Allocation.visit({ id: allocation.id });
   202  
   203      assert.ok(Allocation.tasks[0].hasProxyTag);
   204    });
   205  
   206    test('when there are no tasks, an empty state is shown', async function(assert) {
   207      // Make sure the allocation is pending in order to ensure there are no tasks
   208      allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'pending' });
   209      await Allocation.visit({ id: allocation.id });
   210  
   211      assert.ok(Allocation.isEmpty, 'Task table empty state is shown');
   212    });
   213  
   214    test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function(assert) {
   215      assert.notOk(Allocation.hasRescheduleEvents, 'Reschedule Events section exists');
   216    });
   217  
   218    test('ports are listed', async function(assert) {
   219      const allServerPorts = allocation.taskResources.models[0].resources.Ports;
   220  
   221      allServerPorts.sortBy('Label').forEach((serverPort, index) => {
   222        const renderedPort = Allocation.ports[index];
   223  
   224        assert.equal(renderedPort.name, serverPort.Label);
   225        assert.equal(renderedPort.to, serverPort.To);
   226        const expectedAddr = isIp.v6(serverPort.HostIP)
   227          ? `[${serverPort.HostIP}]:${serverPort.Value}`
   228          : `${serverPort.HostIP}:${serverPort.Value}`;
   229        assert.equal(renderedPort.address, expectedAddr);
   230      });
   231    });
   232  
   233    test('services are listed', async function(assert) {
   234      const taskGroup = server.schema.taskGroups.findBy({ name: allocation.taskGroup });
   235  
   236      assert.equal(Allocation.services.length, taskGroup.services.length);
   237  
   238      taskGroup.services.models.sortBy('name').forEach((serverService, index) => {
   239        const renderedService = Allocation.services[index];
   240  
   241        assert.equal(renderedService.name, serverService.name);
   242        assert.equal(renderedService.port, serverService.portLabel);
   243        assert.equal(renderedService.tags, (serverService.tags || []).join(', '));
   244  
   245        assert.equal(renderedService.connect, serverService.Connect ? 'Yes' : 'No');
   246  
   247        const upstreams = serverService.Connect.SidecarService.Proxy.Upstreams;
   248        const serverUpstreamsString = upstreams
   249          .map(upstream => `${upstream.DestinationName}:${upstream.LocalBindPort}`)
   250          .join(' ');
   251  
   252        assert.equal(renderedService.upstreams, serverUpstreamsString);
   253      });
   254    });
   255  
   256    test('when the allocation is not found, an error message is shown, but the URL persists', async function(assert) {
   257      await Allocation.visit({ id: 'not-a-real-allocation' });
   258  
   259      assert.equal(
   260        server.pretender.handledRequests
   261          .filter(request => !request.url.includes('policy'))
   262          .findBy('status', 404).url,
   263        '/v1/allocation/not-a-real-allocation',
   264        'A request to the nonexistent allocation is made'
   265      );
   266      assert.equal(currentURL(), '/allocations/not-a-real-allocation', 'The URL persists');
   267      assert.ok(Allocation.error.isShown, 'Error message is shown');
   268      assert.equal(Allocation.error.title, 'Not Found', 'Error message is for 404');
   269    });
   270  
   271    test('allocation can be stopped', async function(assert) {
   272      await Allocation.stop.idle();
   273      await Allocation.stop.confirm();
   274  
   275      assert.equal(
   276        server.pretender.handledRequests.findBy('method', 'POST').url,
   277        `/v1/allocation/${allocation.id}/stop`,
   278        'Stop request is made for the allocation'
   279      );
   280    });
   281  
   282    test('allocation can be restarted', async function(assert) {
   283      await Allocation.restart.idle();
   284      await Allocation.restart.confirm();
   285  
   286      assert.equal(
   287        server.pretender.handledRequests.findBy('method', 'PUT').url,
   288        `/v1/client/allocation/${allocation.id}/restart`,
   289        'Restart request is made for the allocation'
   290      );
   291    });
   292  
   293    test('while an allocation is being restarted, the stop button is disabled', async function(assert) {
   294      server.pretender.post('/v1/allocation/:id/stop', () => [204, {}, ''], true);
   295  
   296      await Allocation.stop.idle();
   297  
   298      run.later(() => {
   299        assert.ok(Allocation.stop.isRunning, 'Stop is loading');
   300        assert.ok(Allocation.restart.isDisabled, 'Restart is disabled');
   301        server.pretender.resolve(server.pretender.requestReferences[0].request);
   302      }, 500);
   303  
   304      await Allocation.stop.confirm();
   305    });
   306  
   307    test('if stopping or restarting fails, an error message is shown', async function(assert) {
   308      server.pretender.post('/v1/allocation/:id/stop', () => [403, {}, '']);
   309  
   310      await Allocation.stop.idle();
   311      await Allocation.stop.confirm();
   312  
   313      assert.ok(Allocation.inlineError.isShown, 'Inline error is shown');
   314      assert.ok(
   315        Allocation.inlineError.title.includes('Could Not Stop Allocation'),
   316        'Title is descriptive'
   317      );
   318      assert.ok(
   319        /ACL token.+?allocation lifecycle/.test(Allocation.inlineError.message),
   320        'Message mentions ACLs and the appropriate permission'
   321      );
   322  
   323      await Allocation.inlineError.dismiss();
   324  
   325      assert.notOk(Allocation.inlineError.isShown, 'Inline error is no longer shown');
   326    });
   327  });
   328  
   329  module('Acceptance | allocation detail (rescheduled)', function(hooks) {
   330    setupApplicationTest(hooks);
   331    setupMirage(hooks);
   332  
   333    hooks.beforeEach(async function() {
   334      server.create('agent');
   335  
   336      node = server.create('node');
   337      job = server.create('job', { createAllocations: false });
   338      allocation = server.create('allocation', 'rescheduled');
   339  
   340      await Allocation.visit({ id: allocation.id });
   341    });
   342  
   343    test('when the allocation has been rescheduled, the reschedule events section is rendered', async function(assert) {
   344      assert.ok(Allocation.hasRescheduleEvents, 'Reschedule Events section exists');
   345    });
   346  });
   347  
   348  module('Acceptance | allocation detail (not running)', function(hooks) {
   349    setupApplicationTest(hooks);
   350    setupMirage(hooks);
   351  
   352    hooks.beforeEach(async function() {
   353      server.create('agent');
   354  
   355      node = server.create('node');
   356      job = server.create('job', { createAllocations: false });
   357      allocation = server.create('allocation', { clientStatus: 'pending' });
   358  
   359      await Allocation.visit({ id: allocation.id });
   360    });
   361  
   362    test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function(assert) {
   363      assert.equal(Allocation.resourceCharts.length, 0, 'No resource charts');
   364      assert.equal(
   365        Allocation.resourceEmptyMessage,
   366        "Allocation isn't running",
   367        'Empty message is appropriate'
   368      );
   369    });
   370  
   371    test('the exec and stop/restart buttons are absent', async function(assert) {
   372      assert.notOk(Allocation.execButton.isPresent);
   373      assert.notOk(Allocation.stop.isPresent);
   374      assert.notOk(Allocation.restart.isPresent);
   375    });
   376  });
   377  
   378  module('Acceptance | allocation detail (preemptions)', function(hooks) {
   379    setupApplicationTest(hooks);
   380    setupMirage(hooks);
   381  
   382    hooks.beforeEach(async function() {
   383      server.create('agent');
   384      node = server.create('node');
   385      job = server.create('job', { createAllocations: false });
   386    });
   387  
   388    test('shows a dedicated section to the allocation that preempted this allocation', async function(assert) {
   389      allocation = server.create('allocation', 'preempted');
   390      const preempter = server.schema.find('allocation', allocation.preemptedByAllocation);
   391      const preempterJob = server.schema.find('job', preempter.jobId);
   392      const preempterClient = server.schema.find('node', preempter.nodeId);
   393  
   394      await Allocation.visit({ id: allocation.id });
   395      assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown');
   396      assert.equal(Allocation.preempter.status, preempter.clientStatus, 'Preempter status matches');
   397      assert.equal(Allocation.preempter.name, preempter.name, 'Preempter name matches');
   398      assert.equal(
   399        Allocation.preempter.priority,
   400        preempterJob.priority,
   401        'Preempter priority matches'
   402      );
   403  
   404      await Allocation.preempter.visit();
   405      assert.equal(
   406        currentURL(),
   407        `/allocations/${preempter.id}`,
   408        'Clicking the preempter id navigates to the preempter allocation detail page'
   409      );
   410  
   411      await Allocation.visit({ id: allocation.id });
   412      await Allocation.preempter.visitJob();
   413      assert.equal(
   414        currentURL(),
   415        `/jobs/${preempterJob.id}`,
   416        'Clicking the preempter job link navigates to the preempter job page'
   417      );
   418  
   419      await Allocation.visit({ id: allocation.id });
   420      await Allocation.preempter.visitClient();
   421      assert.equal(
   422        currentURL(),
   423        `/clients/${preempterClient.id}`,
   424        'Clicking the preempter client link navigates to the preempter client page'
   425      );
   426    });
   427  
   428    test('shows a dedicated section to the allocations this allocation preempted', async function(assert) {
   429      allocation = server.create('allocation', 'preempter');
   430      await Allocation.visit({ id: allocation.id });
   431      assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown');
   432    });
   433  
   434    test('each preempted allocation in the table lists basic allocation information', async function(assert) {
   435      allocation = server.create('allocation', 'preempter');
   436      await Allocation.visit({ id: allocation.id });
   437  
   438      const preemption = allocation.preemptedAllocations
   439        .map(id => server.schema.find('allocation', id))
   440        .sortBy('modifyIndex')
   441        .reverse()[0];
   442      const preemptionRow = Allocation.preemptions.objectAt(0);
   443  
   444      assert.equal(
   445        Allocation.preemptions.length,
   446        allocation.preemptedAllocations.length,
   447        'The preemptions table has a row for each preempted allocation'
   448      );
   449  
   450      assert.equal(preemptionRow.shortId, preemption.id.split('-')[0], 'Preemption short id');
   451      assert.equal(
   452        preemptionRow.createTime,
   453        moment(preemption.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'),
   454        'Preemption create time'
   455      );
   456      assert.equal(
   457        preemptionRow.modifyTime,
   458        moment(preemption.modifyTime / 1000000).fromNow(),
   459        'Preemption modify time'
   460      );
   461      assert.equal(preemptionRow.status, preemption.clientStatus, 'Client status');
   462      assert.equal(preemptionRow.jobVersion, preemption.jobVersion, 'Job Version');
   463      assert.equal(
   464        preemptionRow.client,
   465        server.db.nodes.find(preemption.nodeId).id.split('-')[0],
   466        'Node ID'
   467      );
   468  
   469      await preemptionRow.visitClient();
   470      assert.equal(currentURL(), `/clients/${preemption.nodeId}`, 'Node links to node page');
   471    });
   472  
   473    test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function(assert) {
   474      allocation = server.create('allocation', 'preempter', 'preempted');
   475      await Allocation.visit({ id: allocation.id });
   476      assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown');
   477      assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown');
   478    });
   479  });