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 });