src/public/Deployment/Get-AitherMeshStatus.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Get the status of all nodes in the AitherMesh, including failover state. .DESCRIPTION Queries the AitherMesh for registered nodes and their health status. Shows connectivity, failover priority, replicated services, and role. Can also manage nodes: drain, rejoin, promote, remove. .PARAMETER Action Action to perform: Status (default), Drain, Rejoin, Promote, Remove. .PARAMETER NodeId Target node for Drain/Rejoin/Promote/Remove actions. .PARAMETER CoreUrl AitherOS Core URL. Default: http://localhost:8001 .PARAMETER MeshPort MeshCore port. Default: 8125 .PARAMETER Detailed Show per-service health for each node. .PARAMETER PassThru Return result objects. .INPUTS None. .OUTPUTS PSCustomObject — Mesh status. .EXAMPLE Get-AitherMeshStatus .EXAMPLE Get-AitherMeshStatus -Detailed .EXAMPLE Get-AitherMeshStatus -Action Drain -NodeId "lab-server" .NOTES Part of AitherZero module — Deployment category. #> function Get-AitherMeshStatus { [OutputType([PSCustomObject])] [CmdletBinding()] param( [ValidateSet("Status", "Drain", "Rejoin", "Promote", "Remove")] [string]$Action = "Status", [string]$NodeId, [string]$CoreUrl = "http://localhost:8001", [int]$MeshPort = 8125, [switch]$Detailed, [switch]$PassThru ) $meshUrl = $CoreUrl -replace ':\d+$', ":$MeshPort" # ── Actions that target a specific node ────────────────── if ($Action -ne "Status") { if (-not $NodeId) { throw "NodeId is required for action '$Action'" } $endpoint = switch ($Action) { "Drain" { "/mesh/drain" } "Rejoin" { "/mesh/rejoin" } "Promote" { "/mesh/promote" } "Remove" { "/mesh/nodes/$NodeId" } } $method = if ($Action -eq "Remove") { "DELETE" } else { "POST" } $body = @{ node_id = $NodeId; action = $Action.ToLower() } | ConvertTo-Json try { $result = Invoke-RestMethod -Uri "${meshUrl}${endpoint}" -Method $method ` -Body $body -ContentType "application/json" -TimeoutSec 10 Write-Host " ✓ $Action on $NodeId — $($result.status ?? 'ok')" -ForegroundColor Green if ($PassThru) { return $result } return } catch { Write-Host " ✗ $Action on $NodeId failed: $($_.Exception.Message)" -ForegroundColor Red return } } # ── Status display ─────────────────────────────────────── Write-Host "" Write-Host " ╔════════════════════════════════════════════════════╗" -ForegroundColor Cyan Write-Host " ║ AitherMesh Status ║" -ForegroundColor Cyan Write-Host " ╚════════════════════════════════════════════════════╝" -ForegroundColor Cyan Write-Host " Mesh endpoint: $meshUrl" -ForegroundColor DarkGray Write-Host "" # Get mesh nodes try { $meshData = Invoke-RestMethod -Uri "$meshUrl/mesh/nodes" -TimeoutSec 10 -ErrorAction Stop $nodes = if ($meshData.nodes) { $meshData.nodes } elseif ($meshData -is [array]) { $meshData } else { @() } } catch { Write-Host " ✗ Cannot reach MeshCore at $meshUrl" -ForegroundColor Red Write-Host " Error: $($_.Exception.Message)" -ForegroundColor DarkGray Write-Host "" Write-Host " Is AitherOS Core running? Try:" -ForegroundColor Yellow Write-Host " docker ps --filter name=aitheros-meshcore" -ForegroundColor DarkGray Write-Host "" if ($PassThru) { return [PSCustomObject]@{ Status = 'Unreachable'; Nodes = @() } } return } if ($nodes.Count -eq 0) { Write-Host " No nodes registered in mesh" -ForegroundColor Yellow Write-Host " Deploy a node: Invoke-AitherElysiumDeploy -ComputerName <host>" -ForegroundColor DarkGray Write-Host "" if ($PassThru) { return [PSCustomObject]@{ Status = 'Empty'; Nodes = @() } } return } # Display each node $nodeResults = @() foreach ($node in $nodes) { $nid = $node.node_id ?? $node.id ?? "unknown" $nurl = $node.node_url ?? $node.url ?? "" $role = $node.role ?? "compute" $priority = $node.failover_priority ?? $node.priority ?? "?" $lastSeen = $node.last_heartbeat ?? $node.last_seen ?? "" # Check health $healthy = $false if ($nurl) { try { Invoke-RestMethod -Uri "$nurl/health" -TimeoutSec 3 -ErrorAction Stop | Out-Null $healthy = $true } catch { } } $statusIcon = if ($healthy) { "●" } else { "○" } $statusColor = if ($healthy) { "Green" } else { "Red" } $statusText = if ($healthy) { "ONLINE" } else { "OFFLINE" } Write-Host " $statusIcon $nid" -ForegroundColor $statusColor -NoNewline Write-Host " — $statusText" -ForegroundColor $statusColor -NoNewline Write-Host " | Role: $role | Priority: $priority" -ForegroundColor DarkGray if ($nurl) { Write-Host " URL: $nurl" -ForegroundColor DarkGray } if ($lastSeen) { Write-Host " Last seen: $lastSeen" -ForegroundColor DarkGray } if ($Detailed -and $healthy -and $nurl) { $services = @( @{ Name = "Genesis"; Port = 8001 }, @{ Name = "Pulse"; Port = 8081 }, @{ Name = "Watch"; Port = 8082 }, @{ Name = "Mesh"; Port = 8125 }, @{ Name = "Strata"; Port = 8136 } ) $baseUrl = $nurl -replace ':\d+$', '' foreach ($svc in $services) { try { Invoke-RestMethod -Uri "${baseUrl}:$($svc.Port)/health" -TimeoutSec 2 -ErrorAction Stop | Out-Null Write-Host " ✓ $($svc.Name):$($svc.Port)" -ForegroundColor Green } catch { Write-Host " ✗ $($svc.Name):$($svc.Port)" -ForegroundColor Red } } } $nodeResults += [PSCustomObject]@{ NodeId = $nid NodeUrl = $nurl Role = $role Priority = $priority Healthy = $healthy LastSeen = $lastSeen } Write-Host "" } $onlineCount = ($nodeResults | Where-Object { $_.Healthy }).Count Write-Host " Summary: $onlineCount/$($nodeResults.Count) nodes online" -ForegroundColor $(if ($onlineCount -eq $nodeResults.Count) { "Green" } else { "Yellow" }) Write-Host "" if ($PassThru) { return [PSCustomObject]@{ PSTypeName = 'AitherOS.MeshStatus' Status = if ($onlineCount -eq $nodeResults.Count) { 'AllHealthy' } elseif ($onlineCount -gt 0) { 'Degraded' } else { 'AllDown' } OnlineNodes = $onlineCount TotalNodes = $nodeResults.Count Nodes = $nodeResults MeshUrl = $meshUrl Timestamp = Get-Date } } } |