github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/topology-test.js (about)

     1  /* eslint-disable qunit/require-expect */
     2  import { get } from '@ember/object';
     3  import { currentURL, typeIn, click } from '@ember/test-helpers';
     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 Topology from 'nomad-ui/tests/pages/topology';
     9  import {
    10    formatBytes,
    11    formatScheduledBytes,
    12    formatHertz,
    13    formatScheduledHertz,
    14  } from 'nomad-ui/utils/units';
    15  import queryString from 'query-string';
    16  import percySnapshot from '@percy/ember';
    17  import faker from 'nomad-ui/mirage/faker';
    18  
    19  const sumResources = (list, dimension) =>
    20    list.reduce((agg, val) => agg + (get(val, dimension) || 0), 0);
    21  
    22  module('Acceptance | topology', function (hooks) {
    23    setupApplicationTest(hooks);
    24    setupMirage(hooks);
    25  
    26    hooks.beforeEach(function () {
    27      server.create('job', { createAllocations: false });
    28    });
    29  
    30    test('it passes an accessibility audit', async function (assert) {
    31      assert.expect(1);
    32  
    33      server.createList('node', 3);
    34      server.createList('allocation', 5);
    35  
    36      await Topology.visit();
    37      await a11yAudit(assert);
    38    });
    39  
    40    test('by default the info panel shows cluster aggregate stats', async function (assert) {
    41      faker.seed(1);
    42      server.createList('node', 3);
    43      server.createList('allocation', 5);
    44  
    45      await Topology.visit();
    46  
    47      await percySnapshot(assert);
    48  
    49      assert.equal(Topology.infoPanelTitle, 'Cluster Details');
    50      assert.notOk(Topology.filteredNodesWarning.isPresent);
    51  
    52      assert.equal(
    53        Topology.clusterInfoPanel.nodeCount,
    54        `${server.schema.nodes.all().length} Clients`
    55      );
    56  
    57      const allocs = server.schema.allocations.all().models;
    58      const scheduledAllocs = allocs.filter((alloc) =>
    59        ['pending', 'running'].includes(alloc.clientStatus)
    60      );
    61      assert.equal(
    62        Topology.clusterInfoPanel.allocCount,
    63        `${scheduledAllocs.length} Allocations`
    64      );
    65  
    66      const nodeResources = server.schema.nodes
    67        .all()
    68        .models.mapBy('nodeResources');
    69      const taskResources = scheduledAllocs
    70        .mapBy('taskResources.models')
    71        .flat()
    72        .mapBy('resources');
    73  
    74      const totalMem = sumResources(nodeResources, 'Memory.MemoryMB');
    75      const totalCPU = sumResources(nodeResources, 'Cpu.CpuShares');
    76      const reservedMem = sumResources(taskResources, 'Memory.MemoryMB');
    77      const reservedCPU = sumResources(taskResources, 'Cpu.CpuShares');
    78  
    79      assert.equal(
    80        Topology.clusterInfoPanel.memoryProgressValue,
    81        reservedMem / totalMem
    82      );
    83      assert.equal(
    84        Topology.clusterInfoPanel.cpuProgressValue,
    85        reservedCPU / totalCPU
    86      );
    87  
    88      assert.equal(
    89        Topology.clusterInfoPanel.memoryAbsoluteValue,
    90        `${formatBytes(reservedMem * 1024 * 1024)} / ${formatBytes(
    91          totalMem * 1024 * 1024
    92        )} reserved`
    93      );
    94  
    95      assert.equal(
    96        Topology.clusterInfoPanel.cpuAbsoluteValue,
    97        `${formatHertz(reservedCPU, 'MHz')} / ${formatHertz(
    98          totalCPU,
    99          'MHz'
   100        )} reserved`
   101      );
   102    });
   103  
   104    test('all allocations for all namespaces and all clients are queried on load', async function (assert) {
   105      server.createList('node', 3);
   106      server.createList('allocation', 5);
   107  
   108      await Topology.visit();
   109      const requests = this.server.pretender.handledRequests;
   110      assert.ok(requests.findBy('url', '/v1/nodes?resources=true'));
   111  
   112      const allocationsRequest = requests.find((req) =>
   113        req.url.startsWith('/v1/allocations')
   114      );
   115      assert.ok(allocationsRequest);
   116  
   117      const allocationRequestParams = queryString.parse(
   118        allocationsRequest.url.split('?')[1]
   119      );
   120      assert.deepEqual(allocationRequestParams, {
   121        namespace: '*',
   122        task_states: 'false',
   123        resources: 'true',
   124      });
   125    });
   126  
   127    test('when an allocation is selected, the info panel shows information on the allocation', async function (assert) {
   128      const nodes = server.createList('node', 5);
   129      const job = server.create('job', { createAllocations: false });
   130      const taskGroup = server.schema.find('taskGroup', job.taskGroupIds[0]).name;
   131      const allocs = server.createList('allocation', 5, {
   132        forceRunningClientStatus: true,
   133        jobId: job.id,
   134        taskGroup,
   135      });
   136  
   137      // Get the first alloc of the first node that has an alloc
   138      const sortedNodes = nodes.sortBy('datacenter');
   139      let node, alloc;
   140      for (let n of sortedNodes) {
   141        alloc = allocs.find((a) => a.nodeId === n.id);
   142        if (alloc) {
   143          node = n;
   144          break;
   145        }
   146      }
   147  
   148      const dcIndex = nodes
   149        .mapBy('datacenter')
   150        .uniq()
   151        .sort()
   152        .indexOf(node.datacenter);
   153      const nodeIndex = nodes
   154        .filterBy('datacenter', node.datacenter)
   155        .indexOf(node);
   156  
   157      const reset = async () => {
   158        await Topology.visit();
   159        await Topology.viz.datacenters[dcIndex].nodes[
   160          nodeIndex
   161        ].memoryRects[0].select();
   162      };
   163  
   164      await reset();
   165      assert.equal(Topology.infoPanelTitle, 'Allocation Details');
   166  
   167      assert.equal(Topology.allocInfoPanel.id, alloc.id.split('-')[0]);
   168  
   169      const uniqueClients = allocs.mapBy('nodeId').uniq();
   170      assert.equal(
   171        Topology.allocInfoPanel.siblingAllocs,
   172        `Sibling Allocations: ${allocs.length}`
   173      );
   174      assert.equal(
   175        Topology.allocInfoPanel.uniquePlacements,
   176        `Unique Client Placements: ${uniqueClients.length}`
   177      );
   178  
   179      assert.equal(Topology.allocInfoPanel.job, job.name);
   180      assert.ok(Topology.allocInfoPanel.taskGroup.endsWith(alloc.taskGroup));
   181      assert.equal(Topology.allocInfoPanel.client, node.id.split('-')[0]);
   182  
   183      await Topology.allocInfoPanel.visitAlloc();
   184      assert.equal(currentURL(), `/allocations/${alloc.id}`);
   185  
   186      await reset();
   187  
   188      await Topology.allocInfoPanel.visitJob();
   189      assert.equal(currentURL(), `/jobs/${job.id}@default`);
   190  
   191      await reset();
   192  
   193      await Topology.allocInfoPanel.visitClient();
   194      assert.equal(currentURL(), `/clients/${node.id}`);
   195    });
   196  
   197    test('changing which allocation is selected changes the metric charts', async function (assert) {
   198      server.create('node');
   199      const job1 = server.create('job', { createAllocations: false });
   200      const taskGroup1 = server.schema.find(
   201        'taskGroup',
   202        job1.taskGroupIds[0]
   203      ).name;
   204      server.create('allocation', {
   205        forceRunningClientStatus: true,
   206        jobId: job1.id,
   207        taskGroup1,
   208      });
   209  
   210      const job2 = server.create('job', { createAllocations: false });
   211      const taskGroup2 = server.schema.find(
   212        'taskGroup',
   213        job2.taskGroupIds[0]
   214      ).name;
   215      server.create('allocation', {
   216        forceRunningClientStatus: true,
   217        jobId: job2.id,
   218        taskGroup2,
   219      });
   220  
   221      await Topology.visit();
   222      await Topology.viz.datacenters[0].nodes[0].memoryRects[0].select();
   223      const firstAllocationTaskNames =
   224        Topology.allocInfoPanel.charts[0].areas.mapBy('taskName');
   225  
   226      await Topology.viz.datacenters[0].nodes[0].memoryRects[1].select();
   227      const secondAllocationTaskNames =
   228        Topology.allocInfoPanel.charts[0].areas.mapBy('taskName');
   229  
   230      assert.notDeepEqual(firstAllocationTaskNames, secondAllocationTaskNames);
   231    });
   232  
   233    test('when a node is selected, the info panel shows information on the node', async function (assert) {
   234      // A high node count is required for node selection
   235      const nodes = server.createList('node', 51);
   236      const node = nodes.sortBy('datacenter')[0];
   237      server.createList('allocation', 5, { forceRunningClientStatus: true });
   238  
   239      const allocs = server.schema.allocations.where({ nodeId: node.id }).models;
   240  
   241      await Topology.visit();
   242  
   243      await Topology.viz.datacenters[0].nodes[0].selectNode();
   244      assert.equal(Topology.infoPanelTitle, 'Client Details');
   245  
   246      assert.equal(Topology.nodeInfoPanel.id, node.id.split('-')[0]);
   247      assert.equal(Topology.nodeInfoPanel.name, `Name: ${node.name}`);
   248      assert.equal(Topology.nodeInfoPanel.address, `Address: ${node.httpAddr}`);
   249      assert.equal(Topology.nodeInfoPanel.status, `Status: ${node.status}`);
   250  
   251      assert.equal(
   252        Topology.nodeInfoPanel.drainingLabel,
   253        node.drain ? 'Yes' : 'No'
   254      );
   255      assert.equal(
   256        Topology.nodeInfoPanel.eligibleLabel,
   257        node.schedulingEligibility === 'eligible' ? 'Yes' : 'No'
   258      );
   259  
   260      assert.equal(Topology.nodeInfoPanel.drainingIsAccented, node.drain);
   261      assert.equal(
   262        Topology.nodeInfoPanel.eligibleIsAccented,
   263        node.schedulingEligibility !== 'eligible'
   264      );
   265  
   266      const taskResources = allocs
   267        .mapBy('taskResources.models')
   268        .flat()
   269        .mapBy('resources');
   270      const reservedMem = sumResources(taskResources, 'Memory.MemoryMB');
   271      const reservedCPU = sumResources(taskResources, 'Cpu.CpuShares');
   272  
   273      const totalMem = node.nodeResources.Memory.MemoryMB;
   274      const totalCPU = node.nodeResources.Cpu.CpuShares;
   275  
   276      assert.equal(
   277        Topology.nodeInfoPanel.memoryProgressValue,
   278        reservedMem / totalMem
   279      );
   280      assert.equal(
   281        Topology.nodeInfoPanel.cpuProgressValue,
   282        reservedCPU / totalCPU
   283      );
   284  
   285      assert.equal(
   286        Topology.nodeInfoPanel.memoryAbsoluteValue,
   287        `${formatScheduledBytes(
   288          reservedMem * 1024 * 1024
   289        )} / ${formatScheduledBytes(totalMem, 'MiB')} reserved`
   290      );
   291  
   292      assert.equal(
   293        Topology.nodeInfoPanel.cpuAbsoluteValue,
   294        `${formatScheduledHertz(reservedCPU, 'MHz')} / ${formatScheduledHertz(
   295          totalCPU,
   296          'MHz'
   297        )} reserved`
   298      );
   299  
   300      await Topology.nodeInfoPanel.visitNode();
   301      assert.equal(currentURL(), `/clients/${node.id}`);
   302    });
   303  
   304    test('when one or more nodes lack the NodeResources property, a warning message is shown', async function (assert) {
   305      server.createList('node', 3);
   306      server.createList('allocation', 5);
   307  
   308      server.schema.nodes.all().models[0].update({ nodeResources: null });
   309  
   310      await Topology.visit();
   311      assert.ok(Topology.filteredNodesWarning.isPresent);
   312      assert.ok(Topology.filteredNodesWarning.message.startsWith('1'));
   313    });
   314  
   315    test('Filtering and Querying reduces the number of nodes shown', async function (assert) {
   316      server.createList('node', 10);
   317      server.createList('node', 2, {
   318        nodeClass: 'foo-bar-baz',
   319      });
   320      server.createList('allocation', 5);
   321  
   322      await Topology.visit();
   323      assert.dom('[data-test-topo-viz-node]').exists({ count: 12 });
   324  
   325      await typeIn('input.node-search', server.schema.nodes.first().name);
   326      assert.dom('[data-test-topo-viz-node]').exists({ count: 1 });
   327      await typeIn('input.node-search', server.schema.nodes.first().name);
   328      assert.dom('[data-test-topo-viz-node]').doesNotExist();
   329      await click('[title="Clear search"]');
   330      assert.dom('[data-test-topo-viz-node]').exists({ count: 12 });
   331  
   332      await Topology.facets.class.toggle();
   333      await Topology.facets.class.options
   334        .findOneBy('label', 'foo-bar-baz')
   335        .toggle();
   336      assert.dom('[data-test-topo-viz-node]').exists({ count: 2 });
   337    });
   338  });