1 module glustercli.volume_parser; 2 3 import std.process; 4 import std..string; 5 import std.conv; 6 import std.json; 7 import std.algorithm: map; 8 import std.array: array; 9 10 import arsd.dom; 11 12 enum VolumeTypeDist = "Distribute"; 13 enum VolumeTypeRep = "Replicate"; 14 enum VolumeTypeDisp = "Disperse"; 15 enum VolumeTypeDistRep = "Distributed Replicate"; 16 enum VolumeTypeDistDisp = "Distributed Disperse"; 17 18 enum SubvolTypeDist = "Distribute"; 19 enum SubvolTypeRep = "Replicate"; 20 enum SubvolTypeDisp = "Disperse"; 21 22 enum BrickTypeDefault = "Brick"; 23 enum BrickTypeArbiter = "Arbiter"; 24 25 enum HealthUp = "up"; 26 enum HealthDown = "down"; 27 enum HealthPartial = "partial"; 28 enum HealthDegraded = "degraded"; 29 30 enum StateCreated = "Created"; 31 enum StateStarted = "Started"; 32 enum StateStopped = "Stopped"; 33 34 struct Volume 35 { 36 string name; 37 string type; 38 string state; 39 string health; 40 string id; 41 int numSubvols; 42 int numBricks; 43 int replicaCount; 44 int arbiterCount; 45 int disperseCount; 46 int disperseRedundancyCount; 47 string transport; 48 SubVolume[] subvols; 49 ulong sizeTotal; 50 ulong sizeUsed; 51 ulong inodesTotal; 52 ulong inodesUsed; 53 Option[] options; 54 JSONValue toJson() 55 { 56 return JSONValue( 57 [ 58 "name": JSONValue(name), 59 "type": JSONValue(type), 60 "state": JSONValue(state), 61 "health": JSONValue(health), 62 "id": JSONValue(id), 63 "num_subvols": JSONValue(numSubvols), 64 "num_bricks": JSONValue(numBricks), 65 "replica_count": JSONValue(replicaCount), 66 "arbiter_count": JSONValue(arbiterCount), 67 "disperse_count": JSONValue(disperseCount), 68 "disperse_redundancy_count": JSONValue(disperseRedundancyCount), 69 "transport": JSONValue(transport), 70 "subvols": JSONValue(subvols.map!(subvol => subvol.toJson).array), 71 "size_total": JSONValue(sizeTotal), 72 "size_used": JSONValue(sizeUsed), 73 "inodes_total": JSONValue(inodesTotal), 74 "inodes_used": JSONValue(inodesUsed), 75 "options": JSONValue(options.map!(opt => opt.toJson).array), 76 ] 77 ); 78 } 79 } 80 81 struct Option 82 { 83 string name; 84 string value; 85 string volumeId; 86 JSONValue toJson() 87 { 88 return JSONValue( 89 [ 90 "name": JSONValue(name), 91 "value": JSONValue(value), 92 "volumes_id": JSONValue(volumeId) 93 ] 94 ); 95 } 96 } 97 98 struct SubVolume 99 { 100 string id; 101 string health; 102 int replicaCount; 103 int arbiterCount; 104 int disperseCount; 105 int disperseRedundancyCount; 106 string type; 107 Brick[] bricks; 108 string volumeId; 109 ulong numBricks; 110 JSONValue toJson() 111 { 112 return JSONValue( 113 [ 114 "id": JSONValue(id), 115 "health": JSONValue(health), 116 "replica_count": JSONValue(replicaCount), 117 "arbiter_count": JSONValue(arbiterCount), 118 "disperse_count": JSONValue(disperseCount), 119 "disperse_redundancy_count": JSONValue(disperseRedundancyCount), 120 "type": JSONValue(type), 121 "bricks": JSONValue(bricks.map!(brick => brick.toJson).array), 122 "num_bricks": JSONValue(numBricks), 123 "volumes_id": JSONValue(volumeId) 124 ] 125 ); 126 } 127 } 128 129 struct Brick 130 { 131 string host; 132 string path; 133 string nodeid; 134 string state; 135 string type; 136 int port; 137 int pid; 138 ulong sizeTotal; 139 ulong sizeUsed; 140 ulong inodesTotal; 141 ulong inodesUsed; 142 string fsType; 143 string device; 144 int blockSize; 145 string mountOptions; 146 string volumeId; 147 string subvolId; 148 JSONValue toJson() 149 { 150 return JSONValue( 151 [ 152 "host": JSONValue(host), 153 "path": JSONValue(path), 154 "peers_id": JSONValue(nodeid), 155 "state": JSONValue(state), 156 "type": JSONValue(type), 157 "port": JSONValue(port), 158 "pid": JSONValue(pid), 159 "size_total": JSONValue(sizeTotal), 160 "size_used": JSONValue(sizeUsed), 161 "inodes_total": JSONValue(inodesTotal), 162 "inodes_used": JSONValue(inodesUsed), 163 "fs": JSONValue(fsType), 164 "device": JSONValue(device), 165 "block_size": JSONValue(blockSize), 166 "mount_options": JSONValue(mountOptions), 167 "volumes_id": JSONValue(volumeId), 168 "subvols_id": JSONValue(subvolId), 169 ] 170 ); 171 } 172 } 173 174 string transportType(string val) 175 { 176 if (val == "0") 177 return "TCP"; 178 else if (val == "1") 179 return "RDMA"; 180 181 return "TCP,RDMA"; 182 } 183 184 int parsePortOrPid(string val) 185 { 186 try 187 { 188 return to!int(val); 189 } 190 catch (ConvException) 191 { 192 return -1; 193 } 194 } 195 196 Brick[string] parseBricksFromVolStatus(Element[] elements) 197 { 198 Brick[string] bricks; 199 foreach(ele; elements) 200 { 201 auto brick = Brick(); 202 brick.host = ele.querySelector("hostname").innerText; 203 brick.path = ele.querySelector("path").innerText; 204 brick.nodeid = ele.querySelector("peerid").innerText; 205 brick.fsType = ele.querySelector("fsName").innerText; 206 brick.device = ele.querySelector("device").innerText; 207 brick.blockSize = to!int(ele.querySelector("blockSize").innerText); 208 brick.mountOptions = ele.querySelector("mntOptions").innerText; 209 brick.state = (ele.querySelector("status").innerText == "1" ? HealthUp : HealthDown); 210 brick.sizeTotal = to!ulong(ele.querySelector("sizeTotal").innerText); 211 brick.sizeUsed = brick.sizeTotal - to!ulong(ele.querySelector("sizeFree").innerText); 212 brick.inodesTotal = to!ulong(ele.querySelector("inodesTotal").innerText); 213 brick.inodesUsed = brick.inodesTotal - to!ulong(ele.querySelector("inodesFree").innerText); 214 brick.port = parsePortOrPid(ele.querySelector("port").innerText); 215 brick.pid = parsePortOrPid(ele.querySelector("pid").innerText); 216 217 bricks[brick.host ~ ":" ~ brick.path] = brick; 218 } 219 return bricks; 220 } 221 222 223 Option[] parseOptionsFromVolinfo(Volume vol, Element[] elements) 224 { 225 Option[] options; 226 foreach(ele; elements) 227 { 228 options ~= Option( 229 ele.querySelector("name").innerText, 230 ele.querySelector("value").innerText, 231 vol.id 232 ); 233 } 234 return options; 235 } 236 237 Brick[] parseBricksFromVolinfo(Element[] elements) 238 { 239 Brick[] bricks; 240 foreach(ele; elements) 241 { 242 auto brick = Brick(); 243 auto hostAndPath = ele.querySelector("name").innerText.split(":"); 244 brick.path = hostAndPath[1]; 245 brick.host = hostAndPath[0]; 246 brick.nodeid = ele.querySelector("hostUuid").innerText; 247 brick.type = BrickTypeDefault; 248 brick.type = (ele.querySelector("isArbiter").innerText == "1" ? BrickTypeArbiter : BrickTypeDefault); 249 250 brick.blockSize = 4096; 251 brick.state = "Unknown"; 252 bricks ~= brick; 253 } 254 return bricks; 255 } 256 257 int getSubvolBricksCount(int replicaCount, int disperseCount) 258 { 259 if (replicaCount > 0) 260 return replicaCount; 261 262 if (disperseCount > 0) 263 return disperseCount; 264 265 return 1; 266 } 267 268 269 string getSubvolType(string voltype) 270 { 271 switch (voltype) 272 { 273 case VolumeTypeDistRep: 274 return SubvolTypeRep; 275 case VolumeTypeDistDisp: 276 return SubvolTypeDisp; 277 default: 278 return voltype; 279 } 280 } 281 282 void parseVolumesFromVolinfo(string[] output, ref Volume[] volumes) 283 { 284 auto document = new Document(output.join("")); 285 auto elements = document.getElementsByTagName("volume"); 286 foreach(ele; elements) 287 { 288 auto vol = Volume(); 289 vol.health = HealthDown; 290 vol.name = ele.querySelector("name").innerText; 291 vol.type = ele.querySelector("typeStr").innerText.replace("-", " "); 292 vol.state = ele.querySelector("statusStr").innerText; 293 vol.id = ele.querySelector("id").innerText; 294 vol.numBricks = to!int(ele.querySelector("brickCount").innerText); 295 vol.replicaCount = to!int(ele.querySelector("replicaCount").innerText); 296 vol.disperseCount = to!int(ele.querySelector("disperseCount").innerText); 297 vol.arbiterCount = to!int(ele.querySelector("arbiterCount").innerText); 298 vol.disperseRedundancyCount = to!int(ele.querySelector("redundancyCount").innerText); 299 vol.transport = transportType(ele.querySelector("transport").innerText); 300 301 vol.options = parseOptionsFromVolinfo(vol, ele.getElementsByTagName("option")); 302 303 // Parse the bricks and create subvol groups 304 auto bricks = parseBricksFromVolinfo(ele.getElementsByTagName("brick")); 305 auto subvolBricksCount = getSubvolBricksCount(vol.replicaCount, vol.disperseCount); 306 vol.numSubvols = vol.numBricks / subvolBricksCount; 307 308 foreach(sidx; 0 .. vol.numSubvols) 309 { 310 auto subvol = SubVolume(); 311 subvol.volumeId = vol.id; 312 subvol.health = HealthDown; 313 subvol.type = getSubvolType(vol.type); 314 subvol.replicaCount = vol.replicaCount; 315 subvol.arbiterCount = vol.arbiterCount; 316 subvol.disperseCount = vol.disperseCount; 317 subvol.disperseRedundancyCount = vol.disperseRedundancyCount; 318 subvol.id = format!"%s-%s-%d"(vol.name, subvol.type.toLower, sidx); 319 foreach(bidx; 0 .. subvolBricksCount) 320 { 321 subvol.bricks ~= bricks[sidx+bidx]; 322 } 323 subvol.numBricks = bricks.length; 324 vol.subvols ~= subvol; 325 } 326 327 volumes ~= vol; 328 } 329 } 330 331 void mergeVolumeInfoAndStatus(ref Volume[] volumes, Brick[string] bricksdata) 332 { 333 foreach(vol; volumes) 334 { 335 foreach(subvol; vol.subvols) 336 { 337 foreach(ref brick; subvol.bricks) 338 { 339 auto name = brick.host ~ ":" ~ brick.path; 340 brick.volumeId = vol.id; 341 brick.subvolId = subvol.id; 342 if ((name in bricksdata) !is null) 343 { 344 brick.fsType = bricksdata[name].fsType; 345 brick.device = bricksdata[name].device; 346 brick.blockSize = bricksdata[name].blockSize; 347 brick.mountOptions = bricksdata[name].mountOptions; 348 brick.state = bricksdata[name].state; 349 brick.sizeUsed = bricksdata[name].sizeUsed; 350 brick.sizeTotal = bricksdata[name].sizeTotal; 351 brick.inodesUsed = bricksdata[name].inodesUsed; 352 brick.inodesTotal = bricksdata[name].inodesTotal; 353 brick.port = bricksdata[name].port; 354 brick.pid = bricksdata[name].pid; 355 } 356 } 357 } 358 } 359 } 360 361 void updateVolumeUtilization(ref Volume[] volumes) 362 { 363 foreach(ref vol; volumes) 364 { 365 foreach(subvol; vol.subvols) 366 { 367 ulong effectiveCapacityUsed, effectiveCapacityTotal; 368 ulong effectiveInodesUsed, effectiveInodesTotal; 369 370 foreach(brick; subvol.bricks) 371 { 372 if (brick.type != BrickTypeArbiter) 373 { 374 if(brick.sizeUsed >= effectiveCapacityUsed) 375 effectiveCapacityUsed = brick.sizeUsed; 376 377 if (effectiveCapacityTotal == 0 || brick.sizeTotal <= effectiveCapacityTotal) 378 effectiveCapacityTotal = brick.sizeTotal; 379 380 if(brick.inodesUsed >= effectiveInodesUsed) 381 effectiveInodesUsed = brick.inodesUsed; 382 383 if (effectiveInodesTotal == 0 || brick.inodesTotal <= effectiveInodesTotal) 384 effectiveInodesTotal = brick.inodesTotal; 385 } 386 } 387 if (subvol.type == SubvolTypeDisp) 388 { 389 // Subvol Size = Sum of size of Data bricks 390 effectiveCapacityUsed = effectiveCapacityUsed * (subvol.disperseCount - subvol.disperseRedundancyCount); 391 effectiveCapacityTotal = effectiveCapacityTotal * (subvol.disperseCount - subvol.disperseRedundancyCount); 392 effectiveInodesUsed = effectiveInodesUsed * (subvol.disperseCount - subvol.disperseRedundancyCount); 393 effectiveInodesTotal = effectiveInodesTotal * (subvol.disperseCount - subvol.disperseRedundancyCount); 394 } 395 396 vol.sizeTotal += effectiveCapacityTotal; 397 vol.sizeUsed += effectiveCapacityUsed; 398 vol.inodesTotal += effectiveInodesTotal; 399 vol.inodesUsed += effectiveInodesUsed; 400 } 401 } 402 } 403 404 void updateVolumeHealth(ref Volume[] volumes) 405 { 406 foreach(ref vol; volumes) 407 { 408 if (vol.state != StateStarted) 409 continue; 410 411 vol.health = HealthUp; 412 auto upSubvols = 0; 413 foreach(ref subvol; vol.subvols) 414 { 415 auto upBricks = 0; 416 foreach(brick; subvol.bricks) 417 { 418 if (brick.state == HealthUp) 419 upBricks++; 420 } 421 subvol.health = HealthUp; 422 if (subvol.bricks.length != upBricks) 423 { 424 subvol.health = HealthDown; 425 if (subvol.type == SubvolTypeRep && upBricks >= (subvol.replicaCount/2 + 1)) 426 subvol.health = HealthPartial; 427 428 // If down bricks are less than or equal to redudancy count 429 // then Volume is UP but some bricks are down 430 if (subvol.type == SubvolTypeDisp && 431 (subvol.bricks.length - upBricks) <= subvol.disperseRedundancyCount) 432 subvol.health = HealthPartial; 433 434 } 435 436 if (subvol.health == HealthDown) 437 vol.health = HealthDegraded; 438 439 if (subvol.health == HealthPartial && vol.health != HealthDegraded) 440 vol.health = subvol.health; 441 442 if (subvol.health != HealthDown) 443 upSubvols++; 444 } 445 if (upSubvols == 0) 446 vol.health = HealthDown; 447 } 448 } 449 450 451 void parseVolumesFromStatus(string[] output, ref Volume[] volumes) 452 { 453 auto document = new Document(output.join("")); 454 auto bricksdata = parseBricksFromVolStatus(document.getElementsByTagName("node")); 455 mergeVolumeInfoAndStatus(volumes, bricksdata); 456 updateVolumeUtilization(volumes); 457 updateVolumeHealth(volumes); 458 }