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