diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9aa1a..78954f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## Release 1.1.4 (2025-01-13) + +[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.3...v1.1.4) + +**Features** + +- Improve API graceful shutdown +- Add control on HealtChecks jobs through CLI + +**Bugfixes** + ## Release 1.1.3 (2025-01-07) [Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.2...v1.1.3) diff --git a/src/WinBGP-API.ps1 b/src/WinBGP-API.ps1 index 5ae11f5..905c4b8 100644 --- a/src/WinBGP-API.ps1 +++ b/src/WinBGP-API.ps1 @@ -23,7 +23,7 @@ Param ( $Configuration=$false ) -$scriptVersion = '1.1.1' +$scriptVersion = '1.1.2' # Create detailled log for WinBGP-API # New-EventLog –LogName Application –Source 'WinBGP-API' -ErrorAction SilentlyContinue @@ -316,6 +316,10 @@ if ($Configuration) { } # Starting listerner $listener.Start() + + # Flag to control the loop + $keepListening = $true + # Output listeners foreach ($ListenerPrefixe in $ListenerPrefixes) { [String]$Protocol = $ListenerPrefixe.Split('://')[0] @@ -324,11 +328,12 @@ if ($Configuration) { Write-Log -Message "API started - Listening on '$($IP):$($Port)' (Protocol: $Protocol)" } - while ($listener.IsListening) { + while (($listener.IsListening) -and ($keepListening)) { # Default return $statusCode = [System.Net.HttpStatusCode]::OK $commandOutput = [string]::Empty $outputHeader = @{} + # Accept incoming request $context = $listener.GetContext() $request = $context.Request [string]$RequestHost=$request.RemoteEndPoint @@ -462,8 +467,8 @@ if ($Configuration) { $peersCurrentStatus=Get-BgpPeer -ErrorAction SilentlyContinue | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}} } catch { - #If BGP Router (Local) is not configured, catch it - $BgpStatus=($_).ToString() + # If BGP Router (Local) is not configured, catch it + $BgpStatus=($_).ToString() } if ($BgpStatus -eq 'BGP is not configured.') { $peersCurrentStatus=$null @@ -497,7 +502,17 @@ if ($Configuration) { } } 'POST' { - if ($FullPath -like 'api/*') { + # Add stop method to stop API (TO IMPROVE) + if ($FullPath -eq 'stop') { + # Only local request are authorized + if ($request.IsLocal) { + $keepListening = $false + $statusCode = [System.Net.HttpStatusCode]::OK + } else { + $statusCode = [System.Net.HttpStatusCode]::Forbidden + } + } + elseif ($FullPath -like 'api/*') { $RouteName = $request.QueryString.Item("RouteName") $Path=$Path.replace('api/','') Write-Log "API received POST request '$Path' from '$RequestUser' - Source IP: '$RequestHost'" -AdditionalFields $RouteName @@ -559,7 +574,10 @@ if ($Configuration) { $output.Write($buffer,0,$buffer.Length) $output.Close() } - $listener.Stop() + if ($listener.IsListening) { + $listener.Stop() + $listener.Close() + } } else { Write-Log -Message "API failed - No Uri listener available" -Level Error } diff --git a/src/WinBGP-Engine.ps1 b/src/WinBGP-Engine.ps1 index 103f304..08c9f09 100644 --- a/src/WinBGP-Engine.ps1 +++ b/src/WinBGP-Engine.ps1 @@ -946,7 +946,7 @@ function Add-RoutePolicy() { # # # Function Start-API # # # -# Description Starting API Engine # +# Description Starting API Job # # # # Arguments See the Param() block at the top of this script # # # @@ -963,6 +963,7 @@ function Start-API() { ) # Start API Write-Log "Starting API engine" + # ArgumentList (,$ApiConfiguration) is to handle array as argument Start-Job -Name 'API' -FilePath "$installDir\$serviceDisplayName-API.ps1" -ArgumentList (,$ApiConfiguration) } @@ -971,7 +972,7 @@ function Start-API() { # # # Function Stop-API # # # -# Description Stopping API Engine # +# Description Stopping API Job # # # # Arguments See the Param() block at the top of this script # # # @@ -983,20 +984,64 @@ function Start-API() { function Stop-API() { # Stop API Write-Log "Stopping API engine" - ### IMPROVEMENT - To be check if we can kill API properly ### - $ProcessID=$null - $ApiPID=$null - # Get service PID - $ProcessID=(Get-CimInstance Win32_Process -Filter "name = 'powershell.exe'" -OperationTimeoutSec 1 | Where-Object {$_.CommandLine -like "*'$installDir\$engineName.ps1' -Service*"}).ProcessId - if ($ProcessID) { - # Get API PID - $ApiPID=(Get-WmiObject win32_process -filter "Name='powershell.exe' AND ParentProcessId=$ProcessID").ProcessId - if ($ApiPID) { - Stop-Process -Id $ApiPID -Force -ErrorAction SilentlyContinue + # Send API stop signal (TO IMPROVE) + try { + if ((Invoke-WebRequest -Uri 'http://127.0.0.1:8888/stop' -Method Post -TimeoutSec 5).StatusCode -eq 200) { + Stop-Job -Name 'API' -ErrorAction SilentlyContinue + Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue } + } catch { + Write-Log "Error stopping API engine: $_" -Level Error } - Stop-Job -Name 'API' -ErrorAction SilentlyContinue - Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue +} + +#-----------------------------------------------------------------------------# +# # +# Function Start-HealthCheck # +# # +# Description Starting HealthCheck Job # +# # +# Arguments See the Param() block at the top of this script # +# # +# Notes # +# # +# History # +# # +#-----------------------------------------------------------------------------# +function Start-HealthCheck() { + Param + ( + [Parameter(Mandatory=$true)] + $Route + ) + # Starting HealthCheck Job + Write-Log "Starting HealthCheck Process" -AdditionalFields @($Route.RouteName) + Start-Job -Name $Route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $Route +} + +#-----------------------------------------------------------------------------# +# # +# Function Stop-HealthCheck # +# # +# Description Stopping API HealthCheck # +# # +# Arguments See the Param() block at the top of this script # +# # +# Notes # +# # +# History # +# # +#-----------------------------------------------------------------------------# +function Stop-HealthCheck() { + Param + ( + [Parameter(Mandatory=$true)] + $Route + ) + # Stopping HealthCheck Job + Write-Log "Stopping HealthCheck Process" -AdditionalFields @($Route.RouteName) + Stop-Job -Name $Route.RouteName + Remove-Job -Name $Route.RouteName -Force } #-----------------------------------------------------------------------------# @@ -1216,8 +1261,7 @@ if ($Service) { # Run the service Write-Log "Route '$($route.RouteName)' is in maintenance mode" -AdditionalFields @($route.RouteName) } else { # Starting HealthCheck Job - Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } } @@ -1307,7 +1351,6 @@ if ($Service) { # Run the service # Start Api Start-API -ApiConfiguration $configuration.api } else { - ### TO BE IMPROVED because killing all healthchecks jobs ### # Stop Api Stop-API } @@ -1340,9 +1383,7 @@ if ($Service) { # Run the service if ($routeReloaded.SideIndicator -eq '<=') { Write-Log "Route '$($routeReloaded.RouteName)' removed" -AdditionalFields @($oldRoute.RouteName) # Stopping HealthCheck Job - Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) - Stop-Job -Name $oldRoute.RouteName - Remove-Job -Name $oldRoute.RouteName -Force + Stop-HealthCheck -Route $oldRoute # Remove routing policy if (get-BgpRoutingPolicy -Name $oldRoute.RouteName -ErrorAction SilentlyContinue) { Write-Log "Removing BGP Routing Policy [$($oldRoute.RouteName)]" -AdditionalFields @($oldRoute.RouteName) @@ -1369,8 +1410,7 @@ if ($Service) { # Run the service # Create routing policies Add-RoutePolicy -Route $route -Peers $configuration.peers # Starting HealthCheck Job - Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } elseif ($routeReloaded.SideIndicator -eq '==') { # Comparing old route and new route to check if there are updates to perform if (($route.Network -ne $oldRoute.Network) -or ($route.DynamicIpSetup -ne $oldRoute.DynamicIpSetup) -or ($route.Interface -ne $oldRoute.Interface) -or ($route.Interval -ne $oldRoute.Interval) -or (Compare-Object -ReferenceObject $oldRoute.Community -DifferenceObject $route.Community) -or ($route.Metric -ne $oldRoute.Metric) -or ($route.NextHop -ne $oldRoute.NextHop) -or ($route.WithdrawOnDown -ne $oldRoute.WithdrawOnDown) -or ($route.WithdrawOnDownCheck -ne $oldRoute.WithdrawOnDownCheck)) { @@ -1382,10 +1422,9 @@ if ($Service) { # Run the service # If WithdrawOnDown change, restart healthcheck Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) # Stopping HealthCheck Job - Stop-Job -Name $oldRoute.RouteName - Remove-Job -Name $oldRoute.RouteName -Force + Stop-HealthCheck -Route $route # Starting HealthCheck Job - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } # Manage WithdrawOnDownCheck change (Only if WithdrawOnDown was enabled and it still enabled) if ($route.WithdrawOnDown -and $oldRoute.WithdrawOnDown) { @@ -1393,22 +1432,18 @@ if ($Service) { # Run the service Write-Log "WithdrawOnDownCheck change - Old Check: '$($oldRoute.WithdrawOnDownCheck)' - New Check: '$($route.WithdrawOnDownCheck)'" -AdditionalFields @($Route.RouteName) Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) # Stopping HealthCheck Job - Stop-Job -Name $oldRoute.RouteName - Remove-Job -Name $oldRoute.RouteName -Force + Stop-HealthCheck -Route $oldRoute # Starting HealthCheck Job - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } } # Manage interval change if ($route.Interval -ne $oldRoute.Interval) { Write-Log "Interval change - Old Interval: '$oldRouteInterval' - New Interval: '$period'" -AdditionalFields @($Route.RouteName) # Stopping HealthCheck Job - Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) - Stop-Job -Name $oldRoute.RouteName - Remove-Job -Name $oldRoute.RouteName -Force + Stop-HealthCheck -Route $oldRoute # Starting HealthCheck Job - Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } # Manage network change if ($route.Network -ne $oldRoute.Network) { @@ -1597,9 +1632,7 @@ if ($Service) { # Run the service # Export maintenance variable on each change (To be moved to function) $maintenance | Export-CliXml -Path $FunctionCliXml -Force # Stopping HealthCheck Job - Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) - Stop-Job -Name $route_maintenance.RouteName - Remove-Job -Name $route_maintenance.RouteName -Force + Stop-HealthCheck -Route $route_maintenance # Removing route if ((Get-BgpCustomRoute).Network -contains "$($route_maintenance.Network)") { remove-Bgp -Route $route_maintenance @@ -1620,8 +1653,7 @@ if ($Service) { # Run the service # Export maintenance variable on each change (To be moved to function) $maintenance | Export-CliXml -Path $FunctionCliXml -Force # Starting HealthCheck Job - Write-Log "Starting HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) - Start-Job -Name $route_maintenance.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route_maintenance + Start-HealthCheck -Route $route_maintenance } else { Write-Log "Route '$($route_maintenance.RouteName)' was not in maintenance mode" -Level Warning @@ -1639,6 +1671,29 @@ if ($Service) { # Run the service # Start another thread waiting for control messages $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" } + elseif ($message -like 'healthcheck*') { + $route_to_control=$message.split(' ')[1] + $control_action=$message.split(' ')[2] + # Grabbing route + $route_healthcheck=$configuration.routes | Where-Object {$_.RouteName -eq $route_to_control} + if ($control_action -eq 'start') { + # Start HealthCheck + Start-HealthCheck -Route $route_healthcheck + } + elseif ($control_action -eq 'stop') { + # Stop HealthCheck + Stop-HealthCheck -Route $route_healthcheck + } elseif ($control_action -eq 'restart') { + # Log + Write-Log "Restarting HealthCheck engine" + # Stop HealthCheck + Stop-HealthCheck -Route $route_healthcheck + # Start HealthCheck + Start-HealthCheck -Route $route_healthcheck + } + # Start another thread waiting for control messages + $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" + } elseif (($message -ne "stop") -and ($message -ne "suspend")) { # Start another thread waiting for control messages $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" } @@ -1673,7 +1728,7 @@ if ($Service) { # Run the service # Cleaning unhealthy HealthCheck Write-Log "Restarting HealthCheck Process (Watchdog)" -AdditionalFields @($route.RouteName) -Level Warning Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue - Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route + Start-HealthCheck -Route $route } } } @@ -1691,9 +1746,7 @@ if ($Service) { # Run the service Write-Log -Message "Stopping HealthCheck engine" ForEach ($route in $configuration.routes) { # Stopping HealthCheck Job - Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route.RouteName) - Stop-Job -Name $route.RouteName -ErrorAction SilentlyContinue - Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue + Stop-HealthCheck -Route $route } # Stopping API diff --git a/src/WinBGP.ps1 b/src/WinBGP.ps1 index c62643c..c2879f6 100644 --- a/src/WinBGP.ps1 +++ b/src/WinBGP.ps1 @@ -124,6 +124,15 @@ Param( [Parameter(ParameterSetName='RouteName', Mandatory=$false)] [Switch]$StopRoute, # Control message to send to the service + [Parameter(ParameterSetName='RouteName', Mandatory=$false)] + [Switch]$StartHealthCheck, # Control message to send to the service + + [Parameter(ParameterSetName='RouteName', Mandatory=$false)] + [Switch]$StopHealthCheck, # Control message to send to the service + + [Parameter(ParameterSetName='RouteName', Mandatory=$false)] + [Switch]$RestartHealthCheck, # Control message to send to the service + [Parameter(ParameterSetName='BGPStatus', Mandatory=$false)] [Switch]$BGPStatus = $($PSCmdlet.ParameterSetName -eq 'BGPStatus'), # Get the current service status @@ -144,7 +153,7 @@ Param( ) # Don't forget to increment version when updating engine -$scriptVersion = '1.0.1' +$scriptVersion = '1.0.2' # This script name, with various levels of details # Ex: PSService @@ -560,9 +569,12 @@ if ($RestartAPI) { } # Start/stop control or Maintenance control -if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) { - # Logging - Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'" +if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance -or $StartHealthCheck -or $StopHealthCheck -or $RestartHealthCheck) { + if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance ) { + # Logging + Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'" + } + # Read configuration $configuration = Get-Content -Path $configdir | ConvertFrom-Json $routeCheck=$null @@ -589,6 +601,21 @@ if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) { $control="maintenance $RouteName stop" } } + # Start/stop control HealthCheck + if ($StartHealthCheck -or $StopHealthCheck -or $RestartHealthCheck) { + # START + if ($StartHealthCheck) { + $control="healthcheck $RouteName start" + } + # STOP + if ($StopHealthCheck) { + $control="healthcheck $RouteName stop" + } + # RESTART + if ($RestartHealthCheck) { + $control="healthcheck $RouteName restart" + } + } if($routeCheck) { $PipeStatus=$null # Performing Action