Merge pull request #4 from webalexeu/fix/api_gracefull_stop

Improve jobs management
This commit is contained in:
Alexandre JARDON
2025-01-13 15:22:03 +01:00
committed by GitHub
4 changed files with 161 additions and 52 deletions

View File

@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file. 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) ## Release 1.1.3 (2025-01-07)
[Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.2...v1.1.3) [Full Changelog](https://github.com/webalexeu/winbgp/compare/v1.1.2...v1.1.3)

View File

@@ -23,7 +23,7 @@ Param (
$Configuration=$false $Configuration=$false
) )
$scriptVersion = '1.1.1' $scriptVersion = '1.1.2'
# Create detailled log for WinBGP-API # Create detailled log for WinBGP-API
# New-EventLog LogName Application Source 'WinBGP-API' -ErrorAction SilentlyContinue # New-EventLog LogName Application Source 'WinBGP-API' -ErrorAction SilentlyContinue
@@ -316,6 +316,10 @@ if ($Configuration) {
} }
# Starting listerner # Starting listerner
$listener.Start() $listener.Start()
# Flag to control the loop
$keepListening = $true
# Output listeners # Output listeners
foreach ($ListenerPrefixe in $ListenerPrefixes) { foreach ($ListenerPrefixe in $ListenerPrefixes) {
[String]$Protocol = $ListenerPrefixe.Split('://')[0] [String]$Protocol = $ListenerPrefixe.Split('://')[0]
@@ -324,11 +328,12 @@ if ($Configuration) {
Write-Log -Message "API started - Listening on '$($IP):$($Port)' (Protocol: $Protocol)" Write-Log -Message "API started - Listening on '$($IP):$($Port)' (Protocol: $Protocol)"
} }
while ($listener.IsListening) { while (($listener.IsListening) -and ($keepListening)) {
# Default return # Default return
$statusCode = [System.Net.HttpStatusCode]::OK $statusCode = [System.Net.HttpStatusCode]::OK
$commandOutput = [string]::Empty $commandOutput = [string]::Empty
$outputHeader = @{} $outputHeader = @{}
# Accept incoming request
$context = $listener.GetContext() $context = $listener.GetContext()
$request = $context.Request $request = $context.Request
[string]$RequestHost=$request.RemoteEndPoint [string]$RequestHost=$request.RemoteEndPoint
@@ -462,7 +467,7 @@ if ($Configuration) {
$peersCurrentStatus=Get-BgpPeer -ErrorAction SilentlyContinue | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}} $peersCurrentStatus=Get-BgpPeer -ErrorAction SilentlyContinue | Select-Object PeerName,LocalIPAddress,LocalASN,PeerIPAddress,PeerASN,@{Label='ConnectivityStatus';Expression={$_.ConnectivityStatus.ToString()}}
} }
catch { catch {
#If BGP Router (Local) is not configured, catch it # If BGP Router (Local) is not configured, catch it
$BgpStatus=($_).ToString() $BgpStatus=($_).ToString()
} }
if ($BgpStatus -eq 'BGP is not configured.') { if ($BgpStatus -eq 'BGP is not configured.') {
@@ -497,7 +502,17 @@ if ($Configuration) {
} }
} }
'POST' { '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") $RouteName = $request.QueryString.Item("RouteName")
$Path=$Path.replace('api/','') $Path=$Path.replace('api/','')
Write-Log "API received POST request '$Path' from '$RequestUser' - Source IP: '$RequestHost'" -AdditionalFields $RouteName 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.Write($buffer,0,$buffer.Length)
$output.Close() $output.Close()
} }
if ($listener.IsListening) {
$listener.Stop() $listener.Stop()
$listener.Close()
}
} else { } else {
Write-Log -Message "API failed - No Uri listener available" -Level Error Write-Log -Message "API failed - No Uri listener available" -Level Error
} }

View File

@@ -946,7 +946,7 @@ function Add-RoutePolicy() {
# # # #
# Function Start-API # # Function Start-API #
# # # #
# Description Starting API Engine # # Description Starting API Job #
# # # #
# Arguments See the Param() block at the top of this script # # Arguments See the Param() block at the top of this script #
# # # #
@@ -963,6 +963,7 @@ function Start-API() {
) )
# Start API # Start API
Write-Log "Starting API engine" Write-Log "Starting API engine"
# ArgumentList (,$ApiConfiguration) is to handle array as argument # ArgumentList (,$ApiConfiguration) is to handle array as argument
Start-Job -Name 'API' -FilePath "$installDir\$serviceDisplayName-API.ps1" -ArgumentList (,$ApiConfiguration) Start-Job -Name 'API' -FilePath "$installDir\$serviceDisplayName-API.ps1" -ArgumentList (,$ApiConfiguration)
} }
@@ -971,7 +972,7 @@ function Start-API() {
# # # #
# Function Stop-API # # Function Stop-API #
# # # #
# Description Stopping API Engine # # Description Stopping API Job #
# # # #
# Arguments See the Param() block at the top of this script # # Arguments See the Param() block at the top of this script #
# # # #
@@ -983,20 +984,64 @@ function Start-API() {
function Stop-API() { function Stop-API() {
# Stop API # Stop API
Write-Log "Stopping API engine" Write-Log "Stopping API engine"
### IMPROVEMENT - To be check if we can kill API properly ### # Send API stop signal (TO IMPROVE)
$ProcessID=$null try {
$ApiPID=$null if ((Invoke-WebRequest -Uri 'http://127.0.0.1:8888/stop' -Method Post -TimeoutSec 5).StatusCode -eq 200) {
# 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
}
}
Stop-Job -Name 'API' -ErrorAction SilentlyContinue Stop-Job -Name 'API' -ErrorAction SilentlyContinue
Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue Remove-Job -Name 'API' -Force -ErrorAction SilentlyContinue
}
} catch {
Write-Log "Error stopping API engine: $_" -Level Error
}
}
#-----------------------------------------------------------------------------#
# #
# 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) Write-Log "Route '$($route.RouteName)' is in maintenance mode" -AdditionalFields @($route.RouteName)
} else { } else {
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} }
} }
@@ -1307,7 +1351,6 @@ if ($Service) { # Run the service
# Start Api # Start Api
Start-API -ApiConfiguration $configuration.api Start-API -ApiConfiguration $configuration.api
} else { } else {
### TO BE IMPROVED because killing all healthchecks jobs ###
# Stop Api # Stop Api
Stop-API Stop-API
} }
@@ -1340,9 +1383,7 @@ if ($Service) { # Run the service
if ($routeReloaded.SideIndicator -eq '<=') { if ($routeReloaded.SideIndicator -eq '<=') {
Write-Log "Route '$($routeReloaded.RouteName)' removed" -AdditionalFields @($oldRoute.RouteName) Write-Log "Route '$($routeReloaded.RouteName)' removed" -AdditionalFields @($oldRoute.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) Stop-HealthCheck -Route $oldRoute
Stop-Job -Name $oldRoute.RouteName
Remove-Job -Name $oldRoute.RouteName -Force
# Remove routing policy # Remove routing policy
if (get-BgpRoutingPolicy -Name $oldRoute.RouteName -ErrorAction SilentlyContinue) { if (get-BgpRoutingPolicy -Name $oldRoute.RouteName -ErrorAction SilentlyContinue) {
Write-Log "Removing BGP Routing Policy [$($oldRoute.RouteName)]" -AdditionalFields @($oldRoute.RouteName) Write-Log "Removing BGP Routing Policy [$($oldRoute.RouteName)]" -AdditionalFields @($oldRoute.RouteName)
@@ -1369,8 +1410,7 @@ if ($Service) { # Run the service
# Create routing policies # Create routing policies
Add-RoutePolicy -Route $route -Peers $configuration.peers Add-RoutePolicy -Route $route -Peers $configuration.peers
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} elseif ($routeReloaded.SideIndicator -eq '==') { } elseif ($routeReloaded.SideIndicator -eq '==') {
# Comparing old route and new route to check if there are updates to perform # 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)) { 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 # If WithdrawOnDown change, restart healthcheck
Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Stop-Job -Name $oldRoute.RouteName Stop-HealthCheck -Route $route
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # 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) # Manage WithdrawOnDownCheck change (Only if WithdrawOnDown was enabled and it still enabled)
if ($route.WithdrawOnDown -and $oldRoute.WithdrawOnDown) { 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 "WithdrawOnDownCheck change - Old Check: '$($oldRoute.WithdrawOnDownCheck)' - New Check: '$($route.WithdrawOnDownCheck)'" -AdditionalFields @($Route.RouteName)
Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName) Write-Log "Restarting HealthCheck Process" -AdditionalFields @($route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Stop-Job -Name $oldRoute.RouteName Stop-HealthCheck -Route $oldRoute
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route Start-HealthCheck -Route $route
} }
} }
# Manage interval change # Manage interval change
if ($route.Interval -ne $oldRoute.Interval) { if ($route.Interval -ne $oldRoute.Interval) {
Write-Log "Interval change - Old Interval: '$oldRouteInterval' - New Interval: '$period'" -AdditionalFields @($Route.RouteName) Write-Log "Interval change - Old Interval: '$oldRouteInterval' - New Interval: '$period'" -AdditionalFields @($Route.RouteName)
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($oldRoute.RouteName) Stop-HealthCheck -Route $oldRoute
Stop-Job -Name $oldRoute.RouteName
Remove-Job -Name $oldRoute.RouteName -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route.RouteName) Start-HealthCheck -Route $route
Start-Job -Name $route.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route
} }
# Manage network change # Manage network change
if ($route.Network -ne $oldRoute.Network) { 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) # Export maintenance variable on each change (To be moved to function)
$maintenance | Export-CliXml -Path $FunctionCliXml -Force $maintenance | Export-CliXml -Path $FunctionCliXml -Force
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) Stop-HealthCheck -Route $route_maintenance
Stop-Job -Name $route_maintenance.RouteName
Remove-Job -Name $route_maintenance.RouteName -Force
# Removing route # Removing route
if ((Get-BgpCustomRoute).Network -contains "$($route_maintenance.Network)") { if ((Get-BgpCustomRoute).Network -contains "$($route_maintenance.Network)") {
remove-Bgp -Route $route_maintenance 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) # Export maintenance variable on each change (To be moved to function)
$maintenance | Export-CliXml -Path $FunctionCliXml -Force $maintenance | Export-CliXml -Path $FunctionCliXml -Force
# Starting HealthCheck Job # Starting HealthCheck Job
Write-Log "Starting HealthCheck Process" -AdditionalFields @($route_maintenance.RouteName) Start-HealthCheck -Route $route_maintenance
Start-Job -Name $route_maintenance.RouteName -FilePath "$installDir\WinBGP-HealthCheck.ps1" -ArgumentList $route_maintenance
} }
else { else {
Write-Log "Route '$($route_maintenance.RouteName)' was not in maintenance mode" -Level Warning 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 # Start another thread waiting for control messages
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" $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 elseif (($message -ne "stop") -and ($message -ne "suspend")) { # Start another thread waiting for control messages
$pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage" $pipeThread = Start-PipeHandlerThread $pipeName -Event "ControlMessage"
} }
@@ -1673,7 +1728,7 @@ if ($Service) { # Run the service
# Cleaning unhealthy HealthCheck # Cleaning unhealthy HealthCheck
Write-Log "Restarting HealthCheck Process (Watchdog)" -AdditionalFields @($route.RouteName) -Level Warning Write-Log "Restarting HealthCheck Process (Watchdog)" -AdditionalFields @($route.RouteName) -Level Warning
Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue 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" Write-Log -Message "Stopping HealthCheck engine"
ForEach ($route in $configuration.routes) { ForEach ($route in $configuration.routes) {
# Stopping HealthCheck Job # Stopping HealthCheck Job
Write-Log "Stopping HealthCheck Process" -AdditionalFields @($route.RouteName) Stop-HealthCheck -Route $route
Stop-Job -Name $route.RouteName -ErrorAction SilentlyContinue
Remove-Job -Name $route.RouteName -Force -ErrorAction SilentlyContinue
} }
# Stopping API # Stopping API

View File

@@ -124,6 +124,15 @@ Param(
[Parameter(ParameterSetName='RouteName', Mandatory=$false)] [Parameter(ParameterSetName='RouteName', Mandatory=$false)]
[Switch]$StopRoute, # Control message to send to the service [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)] [Parameter(ParameterSetName='BGPStatus', Mandatory=$false)]
[Switch]$BGPStatus = $($PSCmdlet.ParameterSetName -eq 'BGPStatus'), # Get the current service status [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 # 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 # This script name, with various levels of details
# Ex: PSService # Ex: PSService
@@ -560,9 +569,12 @@ if ($RestartAPI) {
} }
# Start/stop control or Maintenance control # Start/stop control or Maintenance control
if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) { if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance -or $StartHealthCheck -or $StopHealthCheck -or $RestartHealthCheck) {
if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance ) {
# Logging # Logging
Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'" Write-Log "Operation for route '$RouteName' triggered by '$currentUserName'"
}
# Read configuration # Read configuration
$configuration = Get-Content -Path $configdir | ConvertFrom-Json $configuration = Get-Content -Path $configdir | ConvertFrom-Json
$routeCheck=$null $routeCheck=$null
@@ -589,6 +601,21 @@ if ($StartRoute -or $StopRoute -or $StartMaintenance -or $StopMaintenance) {
$control="maintenance $RouteName stop" $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) { if($routeCheck) {
$PipeStatus=$null $PipeStatus=$null
# Performing Action # Performing Action