github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/nodesOverview/nodesOverview.spec.tsx (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import React from "react";
    12  import { ReactWrapper } from "enzyme";
    13  import { assert } from "chai";
    14  import { times } from "lodash";
    15  import Long from "long";
    16  
    17  import { decommissionedNodesTableDataSelector, liveNodesTableDataSelector, NodeList, NodeStatusRow } from "./index";
    18  import { AdminUIState } from "src/redux/state";
    19  import { LocalSetting } from "src/redux/localsettings";
    20  import { SortSetting } from "src/views/shared/components/sortabletable";
    21  import { connectedMount } from "src/test-utils";
    22  import { cockroach } from "src/js/protos";
    23  import { livenessByNodeIDSelector } from "src/redux/nodes";
    24  
    25  import NodeLivenessStatus = cockroach.kv.kvserver.storagepb.NodeLivenessStatus;
    26  
    27  describe("Nodes Overview page", () => {
    28    describe("Live <NodeList/> section initial state", () => {
    29      const sortSetting = new LocalSetting<AdminUIState, SortSetting>(
    30        "nodes/live_sort_setting", (s) => s.localSettings,
    31      );
    32      const nodesCount = 9;
    33      const regionsCount = 3;
    34  
    35      const dataSource: NodeStatusRow[] = [
    36        {
    37          "key": "us-east1",
    38          "region": "us-east1",
    39          "tiers": [
    40            { "key": "region", "value": "us-west"},
    41            { "key": "az", "value": "us-west-01"},
    42          ],
    43          "nodesCount": 3,
    44          "replicas": 224,
    45          "usedCapacity": 0,
    46          "availableCapacity": 1610612736,
    47          "usedMemory": 1904611328,
    48          "numCpus": 48,
    49          "availableMemory": 51539607552,
    50          "status": 6,
    51          "children": [
    52            {
    53              "key": "us-east1-0",
    54              "nodeId": 1,
    55              "nodeName": "127.0.0.1:50945",
    56              "uptime": "3 minutes",
    57              "replicas": 78,
    58              "usedCapacity": 0,
    59              "availableCapacity": 536870912,
    60              "usedMemory": 639758336,
    61              "numCpus": 16,
    62              "availableMemory": 17179869184,
    63              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
    64              "status": 3,
    65            },
    66            {
    67              "key": "us-east1-1",
    68              "nodeId": 2,
    69              "nodeName": "127.0.0.2:50945",
    70              "uptime": "3 minutes",
    71              "replicas": 74,
    72              "usedCapacity": 0,
    73              "availableCapacity": 536870912,
    74              "usedMemory": 631373824,
    75              "numCpus": 16,
    76              "availableMemory": 17179869184,
    77              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
    78              "status": 3,
    79            },
    80            {
    81              "key": "us-east1-2",
    82              "nodeId": 3,
    83              "nodeName": "127.0.0.3:50945",
    84              "uptime": "3 minutes",
    85              "replicas": 72,
    86              "usedCapacity": 0,
    87              "availableCapacity": 536870912,
    88              "usedMemory": 633479168,
    89              "numCpus": 16,
    90              "availableMemory": 17179869184,
    91              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
    92              "status": 3,
    93            },
    94          ],
    95        },
    96        {
    97          "key": "us-west1",
    98          "region": "us-west1",
    99          "tiers": [
   100            { "key": "region", "value": "us-west"},
   101            { "key": "az", "value": "us-west-01"},
   102          ],
   103          "nodesCount": 3,
   104          "replicas": 229,
   105          "usedCapacity": 0,
   106          "availableCapacity": 1610612736,
   107          "usedMemory": 1913843712,
   108          "numCpus": 48,
   109          "availableMemory": 51539607552,
   110          "status": 6,
   111          "children": [
   112            {
   113              "key": "us-west1-0",
   114              "nodeId": 4,
   115              "nodeName": "127.0.0.4:50945",
   116              "uptime": "3 minutes",
   117              "replicas": 73,
   118              "usedCapacity": 0,
   119              "availableCapacity": 536870912,
   120              "usedMemory": 634728448,
   121              "numCpus": 16,
   122              "availableMemory": 17179869184,
   123              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   124              "status": 3,
   125            },
   126            {
   127              "key": "us-west1-1",
   128              "nodeId": 5,
   129              "nodeName": "127.0.0.5:50945",
   130              "uptime": "3 minutes",
   131              "replicas": 78,
   132              "usedCapacity": 0,
   133              "availableCapacity": 536870912,
   134              "usedMemory": 638218240,
   135              "numCpus": 16,
   136              "availableMemory": 17179869184,
   137              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   138              "status": 3,
   139            },
   140            {
   141              "key": "us-west1-2",
   142              "nodeId": 6,
   143              "nodeName": "127.0.0.6:50945",
   144              "uptime": "3 minutes",
   145              "replicas": 78,
   146              "usedCapacity": 0,
   147              "availableCapacity": 536870912,
   148              "usedMemory": 640897024,
   149              "numCpus": 16,
   150              "availableMemory": 17179869184,
   151              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   152              "status": 3,
   153            },
   154          ],
   155        },
   156        {
   157          "key": "europe-west1",
   158          "region": "europe-west1",
   159          "tiers": [
   160            { "key": "region", "value": "europe-west1"},
   161            { "key": "az", "value": "us-west-01"},
   162          ],
   163          "nodesCount": 3,
   164          "replicas": 216,
   165          "usedCapacity": 0,
   166          "availableCapacity": 1610612736,
   167          "usedMemory": 1924988928,
   168          "numCpus": 48,
   169          "availableMemory": 51539607552,
   170          "status": 6,
   171          "children": [
   172            {
   173              "key": "europe-west1-0",
   174              "nodeId": 7,
   175              "nodeName": "127.0.0.7:50945",
   176              "uptime": "3 minutes",
   177              "replicas": 71,
   178              "usedCapacity": 0,
   179              "availableCapacity": 536870912,
   180              "usedMemory": 641097728,
   181              "numCpus": 16,
   182              "availableMemory": 17179869184,
   183              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   184              "status": 3,
   185            },
   186            {
   187              "key": "europe-west1-1",
   188              "nodeId": 8,
   189              "nodeName": "127.0.0.8:50945",
   190              "uptime": "3 minutes",
   191              "replicas": 74,
   192              "usedCapacity": 0,
   193              "availableCapacity": 536870912,
   194              "usedMemory": 641945600,
   195              "numCpus": 16,
   196              "availableMemory": 17179869184,
   197              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   198              "status": 3,
   199            },
   200            {
   201              "key": "europe-west1-2",
   202              "nodeId": 9,
   203              "nodeName": "127.0.0.9:50945",
   204              "uptime": "3 minutes",
   205              "replicas": 71,
   206              "usedCapacity": 0,
   207              "availableCapacity": 536870912,
   208              "usedMemory": 641945600,
   209              "numCpus": 16,
   210              "availableMemory": 17179869184,
   211              "version": "v20.1.0-alpha.20191118-1798-g0161286a62-dirty",
   212              "status": 3,
   213            },
   214          ],
   215        },
   216      ];
   217  
   218      it("displays correct header of Nodes section with total number of nodes", () => {
   219        const wrapper: ReactWrapper = connectedMount(store => (
   220          <NodeList
   221            dataSource={dataSource}
   222            nodesCount={nodesCount}
   223            regionsCount={regionsCount}
   224            setSort={sortSetting.set}
   225            sortSetting={sortSetting.selector(store.getState())}
   226          />));
   227        assert.equal(wrapper.find("h3.text.text--heading-3").text(), `Nodes (${nodesCount})`);
   228      });
   229  
   230      it("displays table with required columns when nodes partitioned by locality", () => {
   231        const wrapper: ReactWrapper = connectedMount(store => (
   232          <NodeList
   233            dataSource={dataSource}
   234            nodesCount={nodesCount}
   235            regionsCount={regionsCount}
   236            setSort={sortSetting.set}
   237            sortSetting={sortSetting.selector(store.getState())}
   238          />));
   239        const expectedColumns = [
   240          "nodes",
   241          "node count",
   242          "uptime",
   243          "replicas",
   244          "capacity use",
   245          "memory use",
   246          "cpus",
   247          "version",
   248          "status",
   249          "", // logs column doesn't have header text
   250        ];
   251        const columnCells = wrapper.find(".table-section__content table thead th");
   252        assert.equal(columnCells.length, expectedColumns.length);
   253  
   254        expectedColumns.forEach(
   255          (columnName, idx) => assert.equal(columnCells.at(idx).text(), columnName));
   256      });
   257  
   258      it("doesn't display 'node count' column when nodes are in single regions", () => {
   259        const expectedColumns = [
   260          "nodes",
   261          // should not be displayed "node count",
   262          "uptime",
   263          "replicas",
   264          "capacity use",
   265          "memory use",
   266          "cpus",
   267          "version",
   268          "status",
   269          "", // logs column doesn't have header text
   270        ];
   271        const singleRegionDataSource = dataSource[0];
   272        const wrapper = connectedMount(store => (
   273          <NodeList
   274            dataSource={[singleRegionDataSource]}
   275            nodesCount={singleRegionDataSource.children.length}
   276            regionsCount={1}
   277            setSort={sortSetting.set}
   278            sortSetting={sortSetting.selector(store.getState())}
   279          />));
   280        const columnCells = wrapper.find(".table-section__content table thead th");
   281        assert.equal(columnCells.length, expectedColumns.length);
   282        expectedColumns.forEach(
   283          (columnName, idx) => assert.equal(columnCells.at(idx).text(), columnName));
   284      });
   285  
   286      it("displays table with fixed column width", () => {
   287        const wrapper: ReactWrapper = connectedMount(store => (
   288          <NodeList
   289            dataSource={dataSource}
   290            nodesCount={nodesCount}
   291            regionsCount={regionsCount}
   292            setSort={sortSetting.set}
   293            sortSetting={sortSetting.selector(store.getState())}
   294          />));
   295        const columnAttributes = wrapper.find("table colgroup col");
   296        columnAttributes.forEach(node => assert.exists(node.hostNodes().props().style.width));
   297      });
   298    });
   299  
   300    describe("Selectors", () => {
   301      const state = {
   302        cachedData: {
   303          nodes: {
   304            data: times(7).map(idx => (
   305              {
   306                desc: {
   307                  node_id: idx + 1,
   308                  locality: {
   309                    tiers: [
   310                      { key: "region", value: "us-west" },
   311                    ],
   312                  },
   313                  address: {
   314                    address_field: `127.0.0.${idx + 1}:50945`,
   315                  },
   316                },
   317                metrics: {
   318                  "capacity.used": 0,
   319                  "capacity.available": 0,
   320                },
   321                started_at: Long.fromNumber(Date.now()),
   322                total_system_memory: Long.fromNumber(Math.random() * 1000000),
   323                build_info: {
   324                  tag: "tag_value",
   325                },
   326              }
   327            )),
   328            inFlight: false,
   329            valid: true,
   330          },
   331          liveness: {
   332            data: {
   333              livenesses: [
   334                { node_id: 1},
   335                { node_id: 2, expiration: { wall_time: Long.fromNumber(Date.now()) }},
   336                { node_id: 3},
   337                { node_id: 4},
   338                { node_id: 5},
   339                { node_id: 6},
   340                { node_id: 7, expiration: { wall_time: Long.fromNumber(Date.now()) }},
   341              ],
   342              statuses: {
   343                1: NodeLivenessStatus.LIVE,
   344                2: NodeLivenessStatus.DECOMMISSIONED, // node_id: 2
   345                3: NodeLivenessStatus.DEAD,
   346                4: NodeLivenessStatus.UNAVAILABLE,
   347                5: NodeLivenessStatus.UNKNOWN,
   348                6: NodeLivenessStatus.DECOMMISSIONING,
   349                7: NodeLivenessStatus.DECOMMISSIONED, // node_id: 7
   350              },
   351              toJSON: () => ({}),
   352            },
   353            inFlight: false,
   354            valid: true,
   355          },
   356        },
   357      };
   358      const partitionedNodes = {
   359        live: [
   360          state.cachedData.nodes.data[0],
   361          state.cachedData.nodes.data[2],
   362          state.cachedData.nodes.data[3],
   363          state.cachedData.nodes.data[4],
   364          state.cachedData.nodes.data[5],
   365        ],
   366        decommissioned: [
   367          state.cachedData.nodes.data[1],
   368          state.cachedData.nodes.data[6],
   369        ],
   370      };
   371      const nodeSummary: any = {
   372        livenessStatusByNodeID: state.cachedData.liveness.data.statuses,
   373        livenessByNodeID: livenessByNodeIDSelector.resultFunc(state.cachedData.liveness.data),
   374        nodeIDs: undefined,
   375        nodeDisplayNameByID: undefined,
   376        nodeStatusByID: undefined,
   377        nodeStatuses: undefined,
   378        nodeSums: undefined,
   379        storeIDsByNodeID: undefined,
   380      };
   381  
   382      describe("decommissionedNodesTableDataSelector", () => {
   383        it("returns node records with 'decommissioned' status only", () => {
   384          const expectedDecommissionedNodeIds = [2, 7];
   385          const records = decommissionedNodesTableDataSelector.resultFunc(partitionedNodes, nodeSummary);
   386  
   387          assert.lengthOf(records, expectedDecommissionedNodeIds.length);
   388          records.forEach(record => {
   389            assert.isTrue(expectedDecommissionedNodeIds.some(nodeId => nodeId === record.nodeId));
   390          });
   391        });
   392  
   393        it("returns correct node name", () => {
   394          const recordsGroupedByRegion = decommissionedNodesTableDataSelector.resultFunc(partitionedNodes, nodeSummary);
   395          recordsGroupedByRegion.forEach(record => {
   396            const expectedName = `127.0.0.${record.nodeId}:50945`;
   397            assert.equal(record.nodeName, expectedName);
   398          });
   399        });
   400      });
   401  
   402      describe("liveNodesTableDataSelector", () => {
   403        it("returns node records with all statuses except 'decommissioned' status", () => {
   404          const expectedLiveNodeIds = [1, 3, 4, 5, 6];
   405          const recordsGroupedByRegion = liveNodesTableDataSelector.resultFunc(partitionedNodes, nodeSummary);
   406  
   407          assert.lengthOf(recordsGroupedByRegion, 1);
   408          assert.lengthOf(recordsGroupedByRegion[0].children, expectedLiveNodeIds.length);
   409          recordsGroupedByRegion[0].children.forEach(record => {
   410            assert.isTrue(expectedLiveNodeIds.some(nodeId => nodeId === record.nodeId));
   411          });
   412        });
   413  
   414        it("returns correct node name", () => {
   415          const recordsGroupedByRegion = liveNodesTableDataSelector.resultFunc(partitionedNodes, nodeSummary);
   416          recordsGroupedByRegion[0].children.forEach(record => {
   417            const expectedName = `127.0.0.${record.nodeId}:50945`;
   418            assert.equal(record.nodeName, expectedName);
   419          });
   420        });
   421      });
   422    });
   423  });