-
Notifications
You must be signed in to change notification settings - Fork 7
plugin sdk server management
Plugins run as roles that can be assigned to multiple servers for high availability and failover.
Plugin Role
↓
Assigned to Server A (Priority 1)
Assigned to Server B (Priority 2)
Assigned to Server C (Priority 3)
↓
Runs on highest priority available server
↓
Fails over to next server if current fails
Key concepts:
- Plugins are roles that can run on multiple servers
- Priority determines which server runs the plugin
- Automatic failover to next priority server on failure
- Manual server selection supported
- Server rejection allows plugins to control where they run
Servers are assigned to plugin roles in Config Tool:
Path: System > Roles > [Plugin Role] > Properties > Servers
Administrators can:
- Add/remove servers
- Set server priorities
- Enable/disable automatic failover
- Manually select running server
Servers have priority levels (1 = highest):
Priority 1: Primary server (preferred)
Priority 2: Secondary server (backup)
Priority 3: Tertiary server (last resort)
Automatic failover behavior:
- Plugin runs on highest priority available server
- If that server fails, plugin fails over to next priority
- When higher priority server recovers, plugin can failover back (if enabled)
Automatically switches to highest priority server when available:
protected override void OnPluginLoaded()
{
// Enable automatic priority-based failover
SetForceExecutionByPriority(true);
}When enabled:
- Plugin always runs on highest priority available server
- Automatically fails back to higher priority servers when they recover
- Good for active-passive failover scenarios
When disabled:
- The role stays on its current server and does not automatically switch back to a higher-priority server when one recovers
- Moving the role to a specific server requires manual selection
- The plugin runs on only one server at a time
Administrator manually controls which server runs the plugin:
// Get current server
Guid currentServer = GetCurrentServer();
// Manually switch to different server
SetCurrentServer(targetServerGuid);Use cases:
- Maintenance operations
- Load balancing
- Testing
- Troubleshooting
Available through Plugin base class:
Returns GUID of server currently running the plugin:
protected override void OnPluginLoaded()
{
Guid serverGuid = GetCurrentServer();
var server = Engine.GetEntity<Server>(serverGuid);
Logger.TraceInformation($"Plugin running on server: {server.Name}");
}Returns:
- Server GUID
Returns information about all assigned servers:
protected override void OnPluginLoaded()
{
Dictionary<Guid, ServerInfos> servers = GetServersInfo();
foreach (var kvp in servers)
{
Guid serverGuid = kvp.Key;
ServerInfos info = kvp.Value;
Logger.TraceInformation($"Server: {serverGuid}, Order: {info.Order}");
}
}ServerInfos properties:
-
Order- Server priority order (lower numbers = higher priority) -
NetworkBinding- Network binding configuration for the server
Programmatically add a server to the role:
private void AddBackupServer(Guid serverGuid)
{
AddServer(serverGuid);
Logger.TraceInformation($"Added server {serverGuid} to plugin role");
}Considerations:
- Server must exist in system
- Duplicate adds are ignored
- Priority assigned automatically (lowest available)
- Config Tool reflects change immediately
Remove a server from the role:
private void RemoveServer(Guid serverGuid)
{
RemoveRoleServer(serverGuid);
Logger.TraceInformation($"Removed server {serverGuid} from plugin role");
}Considerations:
- The method removes the server from the role's server list; it does not block removing the server the role is currently running on, and it does not require switching to another server first
- Removing the server that currently runs the role affects where the role runs, so change the server list deliberately
- Config Tool reflects change immediately
Manually switch to specific server:
private void FailoverToBackup(Guid backupServerGuid)
{
Logger.TraceInformation($"Failing over to backup server {backupServerGuid}");
SetCurrentServer(backupServerGuid);
}Behavior:
- Stops plugin on current server
- Starts plugin on target server
- May cause brief service interruption
- Target server must be in role's server list
Important
SetCurrentServer throws if ForceExecutionByPriority is enabled. Disable it first with SetForceExecutionByPriority(false) before manually switching servers.
Control automatic priority-based failover:
protected override void OnPluginLoaded()
{
// Enable automatic failover to highest priority server
SetForceExecutionByPriority(true);
}Effect:
-
true- Auto-failover to highest priority available server -
false- Stay on current server until failure
Returns the X509 certificate used by the server hosting the plugin:
protected override void OnPluginLoaded()
{
X509Certificate2 cert = GetServerCertificate();
if (cert != null)
{
Logger.TraceInformation($"Server certificate subject: {cert.Subject}");
Logger.TraceInformation($"Certificate expires: {cert.NotAfter}");
}
}Returns:
-
X509Certificate2- A clone of the server's certificate -
null- If no certificate is available
Usage notes:
- Used for validating server identity or establishing secure connections
- The certificate belongs to the Security Center server hosting the plugin
Returns network cards available on each server assigned to the role:
protected override void OnPluginLoaded()
{
Dictionary<Guid, List<NetworkCard>> networkCards = GetAvailableNetworkCards();
foreach (var kvp in networkCards)
{
Guid serverGuid = kvp.Key;
List<NetworkCard> cards = kvp.Value;
var server = Engine.GetEntity<Server>(serverGuid);
Logger.TraceInformation($"Server {server.Name} has {cards.Count} network cards:");
foreach (var card in cards)
{
Logger.TraceInformation($" MAC: {card.MacAddress}, IP: {card.IpAddress}");
}
}
}Returns:
-
Dictionary<Guid, List<NetworkCard>>- Server GUID mapped to list of network cards -
null- If the role entity cannot be found
NetworkCard properties:
-
MacAddress- The MAC address of the network card (or "Any" for default binding) -
IpAddress- The IPv4 address of the network card
Usage notes:
- Always includes an "Any" option with empty IP address for default network binding
- Use to allow administrators to select specific network interfaces
- Useful for plugins that need to bind to specific network adapters
Sets the network card binding for a specific server:
private void ConfigureNetworkBinding(Guid serverGuid, NetworkCard selectedCard)
{
bool success = SetNetworkCard(serverGuid, selectedCard);
if (success)
{
Logger.TraceInformation($"Network card set to {selectedCard.MacAddress}");
}
else
{
Logger.TraceError("Failed to set network card");
}
}Parameters:
-
serverId- The GUID of the server to configure -
networkCard- The network card to bind to
Returns:
-
true- Network card was set successfully -
false- Role entity not found
Usage notes:
- Setting MAC address to "Any" clears the network binding (uses default)
- Changes are persisted to the role configuration
- The server must be in the role's server list
- Use with
GetAvailableNetworkCards()to present valid options
Plugins can reject servers that don't meet requirements:
Reject current server and trigger failover:
protected override void OnPluginLoaded()
{
if (!ValidateServerRequirements())
{
Guid currentServer = GetCurrentServer();
RejectServer(currentServer, "Required hardware not available");
Logger.TraceWarning($"Rejected server {currentServer}");
return; // Don't continue initialization
}
// Server meets requirements
AcceptServer(GetCurrentServer());
}When rejected:
- Plugin stops on rejected server
- Security Center attempts to start plugin on next priority server
- Rejection reason logged and visible in Config Tool
- Server remains in role's server list but marked as rejected
Explicitly accept current server:
protected override void OnPluginLoaded()
{
if (ValidateServerRequirements())
{
AcceptServer(GetCurrentServer());
Logger.TraceInformation("Server accepted");
}
}When to use:
- After validating server requirements
- Confirms plugin can run on this server
- Good practice for explicit acceptance
Hardware requirements:
private bool ValidateServerRequirements()
{
// Check for required hardware
if (!IsSerialPortAvailable())
{
Logger.TraceError("Serial port not available");
return false;
}
// Check for required network access
if (!CanReachExternalSystem())
{
Logger.TraceError("Cannot reach external system");
return false;
}
return true;
}Resource requirements:
private bool ValidateServerRequirements()
{
// Check available memory
var availableMemory = GetAvailableMemory();
if (availableMemory < RequiredMemoryMB)
{
Logger.TraceError($"Insufficient memory: {availableMemory}MB < {RequiredMemoryMB}MB");
return false;
}
// Check disk space
var availableDisk = GetAvailableDiskSpace();
if (availableDisk < RequiredDiskSpaceGB)
{
Logger.TraceError($"Insufficient disk space");
return false;
}
return true;
}Software requirements:
private bool ValidateServerRequirements()
{
// Check for required assemblies
if (!IsAssemblyAvailable("ThirdParty.Hardware.dll"))
{
Logger.TraceError("Required assembly not found");
return false;
}
// Check for required services
if (!IsServiceRunning("RequiredService"))
{
Logger.TraceError("Required Windows service not running");
return false;
}
return true;
}Plugin running on Server A
↓
Priority changes or the role is moved
↓
1. Dispose() called on Server A
↓
2. Plugin stops on Server A
↓
3. Constructor called on Server B
↓
4. Initialize() called on Server B
↓
5. OnPluginLoaded() called on Server B
↓
6. OnPluginStart() called on Server B
↓
Plugin running on Server B
When Server A fails abruptly, no further code runs there: Dispose() is not called, and the sequence starts at step 3 on Server B. Any work in flight on Server A is abandoned wherever it stopped.
Important
- Complete lifecycle executes on each server
- No state automatically transferred between servers
- Must persist state externally (database, file share)
- Each instance is independent
Use database:
public class MyPlugin : Plugin, IPluginDatabaseSupport
{
protected override void OnPluginStart()
{
// Load state from database
LoadStateFromDatabase();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Save state to database
SaveStateToDatabase();
}
}
}Dispose runs only when the priority changes or the role is moved. When the server fails, Dispose does not run and everything since the last save is lost. Save state when it changes, as the configuration example below does, and treat the save in Dispose as a final write, not the only one.
Use configuration:
protected override void OnPluginLoaded()
{
// Load state from configuration
var config = LoadConfiguration();
RestoreState(config.SavedState);
}
private void OnStateChanged()
{
// Persist state to configuration
var config = LoadConfiguration();
config.SavedState = CaptureState();
UpdateConfiguration(config);
}A plugin role runs on a single server at a time. SetForceExecutionByPriority(false) only stops the role from switching back to a higher-priority server when one recovers; it does not run the plugin on additional servers.
If your plugin must process work concurrently on more than one server, use a workload distribution design outside the single-role failover model.
- Plugin SDK overview: Plugin architecture, lifecycle, and components.
- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
-
Plugin SDK database: SQL Server integration through
IPluginDatabaseSupport. - Plugin SDK state management: Reporting plugin and entity health to Config Tool.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Migrating to modern .NET
- Entities
- Entity cache
- Entity loading, cache, and query options
- Detecting entity changes
- Entity change event reference
- Transactions
- Security Center events
- Actions
- Security Desk
- Custom events
- Event-to-actions
- Managing macros
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Entity write restrictions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- About incidents
- Maps
- Logging
- Overview
- Localization
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Engine extensions
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- Modern .NET support
- Overview
- Localization
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Custom actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- Media file formats
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Endpoints
- Constants
- Enumerations
- Under the hood
- Troubleshooting