github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/redux/nodes.spec.ts (about)

     1  // Copyright 2018 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 { assert } from "chai";
    12  import { createHashHistory } from "history";
    13  
    14  import {MetricConstants, INodeStatus} from "src/util/proto";
    15  import * as protos from "src/js/protos";
    16  
    17  import {
    18    nodeDisplayNameByIDSelector,
    19    selectCommissionedNodeStatuses,
    20    selectStoreIDsByNodeID,
    21    LivenessStatus,
    22    sumNodeStats,
    23  } from "./nodes";
    24  import { nodesReducerObj, livenessReducerObj } from "./apiReducers";
    25  import { createAdminUIStore } from "./state";
    26  
    27  function makeNodesState(...addresses: { id: number, address: string, status?: LivenessStatus }[]) {
    28    const nodeData = addresses.map(addr => {
    29      return {
    30        desc : {
    31          node_id: addr.id,
    32          address: {
    33            address_field: addr.address,
    34          },
    35        },
    36      };
    37    });
    38    const livenessData: {statuses: {[key: string]: LivenessStatus}} = {
    39      statuses: {},
    40    };
    41    addresses.forEach(addr => {
    42      livenessData.statuses[addr.id] = addr.status || LivenessStatus.LIVE;
    43    });
    44    const store = createAdminUIStore(createHashHistory());
    45    store.dispatch(nodesReducerObj.receiveData(nodeData));
    46    store.dispatch(livenessReducerObj.receiveData(new protos.cockroach.server.serverpb.LivenessResponse(livenessData)));
    47    return store.getState();
    48  }
    49  
    50  describe("node data selectors", function() {
    51    describe("display name by ID", function() {
    52      it("display name is node id appended to address", function() {
    53        const state: any = makeNodesState(
    54          { id: 1, address: "addressA" },
    55          { id: 2, address: "addressB" },
    56          { id: 3, address: "addressC" },
    57          { id: 4, address: "addressD" },
    58        );
    59  
    60        const addressesByID = nodeDisplayNameByIDSelector(state);
    61        assert.deepEqual(addressesByID, {
    62          1: "addressA (n1)",
    63          2: "addressB (n2)",
    64          3: "addressC (n3)",
    65          4: "addressD (n4)",
    66        });
    67      });
    68  
    69      it("generates unique names for re-used addresses", function() {
    70        const state: any = makeNodesState(
    71          { id: 1, address: "addressA" },
    72          { id: 2, address: "addressB" },
    73          { id: 3, address: "addressC" },
    74          { id: 4, address: "addressD" },
    75          { id: 5, address: "addressA" },
    76          { id: 6, address: "addressC" },
    77          { id: 7, address: "addressA" },
    78        );
    79  
    80        const addressesByID = nodeDisplayNameByIDSelector(state);
    81        assert.deepEqual(addressesByID, {
    82          1: "addressA (n1)",
    83          2: "addressB (n2)",
    84          3: "addressC (n3)",
    85          4: "addressD (n4)",
    86          5: "addressA (n5)",
    87          6: "addressC (n6)",
    88          7: "addressA (n7)",
    89        });
    90      });
    91  
    92      it("adds decommissioned flag to decommissioned nodes", function() {
    93        const state: any = makeNodesState(
    94          { id: 1, address: "addressA", status: LivenessStatus.DECOMMISSIONED },
    95          { id: 2, address: "addressB" },
    96          { id: 3, address: "addressC", status: LivenessStatus.DECOMMISSIONED },
    97          { id: 4, address: "addressD", status: LivenessStatus.DEAD },
    98          { id: 5, address: "addressA", status: LivenessStatus.DECOMMISSIONED },
    99          { id: 6, address: "addressC" },
   100          { id: 7, address: "addressA" },
   101          { id: 8, address: "addressE", status: LivenessStatus.DECOMMISSIONING },
   102          { id: 9, address: "addressF", status: LivenessStatus.UNAVAILABLE },
   103        );
   104  
   105        const addressesByID = nodeDisplayNameByIDSelector(state);
   106        assert.equal(addressesByID[1], "[decommissioned] addressA (n1)");
   107        assert.deepEqual(addressesByID, {
   108          1: "[decommissioned] addressA (n1)",
   109          2: "addressB (n2)",
   110          3: "[decommissioned] addressC (n3)",
   111          4: "addressD (n4)",
   112          5: "[decommissioned] addressA (n5)",
   113          6: "addressC (n6)",
   114          7: "addressA (n7)",
   115          8: "addressE (n8)",
   116          9: "addressF (n9)",
   117        });
   118      });
   119  
   120      it("returns empty collection for empty state", function() {
   121        const store = createAdminUIStore(createHashHistory());
   122        assert.deepEqual(nodeDisplayNameByIDSelector(store.getState()), {});
   123      });
   124    });
   125  
   126    describe("store IDs by node ID", function() {
   127      it("correctly creates storeID map", function() {
   128        const data = [
   129          {
   130            desc: { node_id: 1 },
   131            store_statuses: [
   132              { desc: { store_id: 1 }},
   133              { desc: { store_id: 2 }},
   134              { desc: { store_id: 3 }},
   135            ],
   136          },
   137          {
   138            desc: { node_id: 2 },
   139            store_statuses: [
   140              { desc: { store_id: 4 }},
   141            ],
   142          },
   143          {
   144            desc: { node_id: 3 },
   145            store_statuses: [
   146              { desc: { store_id: 5 }},
   147              { desc: { store_id: 6 }},
   148            ],
   149          },
   150        ];
   151        const store = createAdminUIStore(createHashHistory());
   152        store.dispatch(nodesReducerObj.receiveData(data));
   153        const state = store.getState();
   154  
   155        assert.deepEqual(selectStoreIDsByNodeID(state), {
   156          1: ["1", "2", "3"],
   157          2: ["4"],
   158          3: ["5", "6"],
   159        });
   160      });
   161    });
   162  });
   163  
   164  describe("selectCommissionedNodeStatuses", function() {
   165    const nodeStatuses: INodeStatus[] = [
   166      {
   167        desc: {
   168          node_id: 1,
   169        },
   170      },
   171    ];
   172  
   173    function makeStateForLiveness(livenessStatuses: { [id: string]: LivenessStatus }) {
   174      return {
   175        cachedData: {
   176          nodes: {
   177            data: nodeStatuses,
   178            inFlight: false,
   179            valid: true,
   180          },
   181          liveness: {
   182            data: {
   183              statuses: livenessStatuses,
   184            },
   185            inFlight: false,
   186            valid: true,
   187          },
   188        },
   189      };
   190    }
   191  
   192    it("selects all nodes when liveness status missing", function() {
   193      const state = makeStateForLiveness({});
   194  
   195      const result = selectCommissionedNodeStatuses(state);
   196  
   197      assert.deepEqual(result, nodeStatuses);
   198    });
   199  
   200    const testCases: [string, LivenessStatus, INodeStatus[]][] = [
   201      ["excludes decommissioned nodes", LivenessStatus.DECOMMISSIONED, []],
   202      ["includes decommissioning nodes", LivenessStatus.DECOMMISSIONING, nodeStatuses],
   203      ["includes live nodes", LivenessStatus.LIVE, nodeStatuses],
   204      ["includes unavailable nodes", LivenessStatus.UNAVAILABLE, nodeStatuses],
   205      ["includes dead nodes", LivenessStatus.DEAD, nodeStatuses],
   206    ];
   207  
   208    testCases.forEach(([name, status, expected]) => {
   209      it(name, function() {
   210        const state = makeStateForLiveness({ "1": status });
   211  
   212        const result = selectCommissionedNodeStatuses(state);
   213  
   214        assert.deepEqual(result, expected);
   215      });
   216    });
   217  });
   218  
   219  describe("sumNodeStats", function() {
   220    it("sums stats from an array of nodes", function() {
   221      // Each of these nodes only has half of its capacity "usable" for cockroach data.
   222      // See diagram for what these stats mean:
   223      // https://github.com/cockroachdb/cockroach/blob/31e4299ab73a43f539b1ba63ed86be5ee18685f6/pkg/storage/metrics.go#L145-L153
   224      const nodeStatuses: INodeStatus[] = [
   225        {
   226          desc: { node_id: 1 },
   227          metrics: {
   228            [MetricConstants.capacity]: 100,
   229            [MetricConstants.usedCapacity]: 10,
   230            [MetricConstants.availableCapacity]: 40,
   231          },
   232        },
   233        {
   234          desc: { node_id: 2 },
   235          metrics: {
   236            [MetricConstants.capacity]: 100,
   237            [MetricConstants.usedCapacity]: 10,
   238            [MetricConstants.availableCapacity]: 40,
   239          },
   240        },
   241      ];
   242      const livenessStatusByNodeID: { [key: string]: LivenessStatus } = {
   243        1: LivenessStatus.LIVE,
   244        2: LivenessStatus.LIVE,
   245      };
   246      const actual = sumNodeStats(nodeStatuses, livenessStatusByNodeID);
   247      assert.equal(actual.nodeCounts.healthy, 2);
   248      assert.equal(actual.capacityTotal, 200);
   249      assert.equal(actual.capacityUsed, 20);
   250      // usable = used + available.
   251      assert.equal(actual.capacityUsable, 100);
   252    });
   253  });