From 3ed191ece002107d31d4fb3ba56fc038fbba3079 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:42:22 +0000 Subject: [PATCH 1/4] Initial plan From 8ab9a1abbf41210773ede12a8ee90c201e2a9319 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:56:38 +0000 Subject: [PATCH 2/4] Add Windows service registration support to azure-cns - Add command-line flags for service management (--service install/uninstall/run) - Implement Windows service handler using golang.org/x/sys/windows/svc - Add automatic service detection when started by Windows Service Manager - Configure service to auto-restart on failure with 5-second delays - Provide event log integration for service lifecycle events - Add stub implementation for non-Windows platforms - Update command-line help to show new service options Co-authored-by: rbtr <2940321+rbtr@users.noreply.github.com> --- cns/service/main.go | 55 +++++++++ cns/service/service_other.go | 31 +++++ cns/service/service_windows.go | 219 +++++++++++++++++++++++++++++++++ common/config.go | 7 ++ 4 files changed, 312 insertions(+) create mode 100644 cns/service/service_other.go create mode 100644 cns/service/service_windows.go diff --git a/cns/service/main.go b/cns/service/main.go index 67f7872f44..28f02e1676 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -346,6 +346,19 @@ var args = acn.ArgumentList{ Type: "string", DefaultValue: "", }, + { + Name: acn.OptServiceAction, + Shorthand: acn.OptServiceActionAlias, + Description: "Windows service action: install, uninstall, or run as service", + Type: "string", + DefaultValue: "", + ValueMap: map[string]interface{}{ + acn.OptServiceInstall: 0, + acn.OptServiceUninstall: 0, + acn.OptServiceRun: 0, + "": 0, + }, + }, } // init() is executed before main() whenever this package is imported @@ -521,12 +534,54 @@ func main() { telemetryDaemonEnabled := acn.GetArg(acn.OptTelemetryService).(bool) cniConflistFilepathArg := acn.GetArg(acn.OptCNIConflistFilepath).(string) cniConflistScenarioArg := acn.GetArg(acn.OptCNIConflistScenario).(string) + serviceAction := acn.GetArg(acn.OptServiceAction).(string) if vers { printVersion() os.Exit(0) } + // Handle Windows service actions (install/uninstall) + switch serviceAction { + case acn.OptServiceInstall: + if err := installService(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to install service: %v\n", err) + os.Exit(1) + } + os.Exit(0) + case acn.OptServiceUninstall: + if err := uninstallService(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to uninstall service: %v\n", err) + os.Exit(1) + } + os.Exit(0) + case acn.OptServiceRun: + // This is an explicit flag to run as service (for testing) + // Normally the service manager would start us and we'd detect it automatically + if err := runAsService(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to run as service: %v\n", err) + os.Exit(1) + } + // The service control loop has exited, but we still need to run the main service logic + // Fall through to continue with normal startup + case "": + // No service action specified, check if we're running as a service + isService, err := isWindowsService() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to detect service mode: %v\n", err) + os.Exit(1) + } + if isService { + // We're being started by the Windows Service Manager + if err := runAsService(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to run as service: %v\n", err) + os.Exit(1) + } + // The service control loop has exited, but we still need to run the main service logic + // Fall through to continue with normal startup + } + } + // Initialize CNS. var ( err error diff --git a/cns/service/service_other.go b/cns/service/service_other.go new file mode 100644 index 0000000000..31b276fe98 --- /dev/null +++ b/cns/service/service_other.go @@ -0,0 +1,31 @@ +//go:build !windows +// +build !windows + +// Copyright 2017 Microsoft. All rights reserved. +// MIT License + +package main + +import ( + "fmt" +) + +// installService is not supported on non-Windows platforms +func installService() error { + return fmt.Errorf("service installation is only supported on Windows") +} + +// uninstallService is not supported on non-Windows platforms +func uninstallService() error { + return fmt.Errorf("service uninstallation is only supported on Windows") +} + +// runAsService is not supported on non-Windows platforms +func runAsService() error { + return fmt.Errorf("running as service is only supported on Windows") +} + +// isWindowsService always returns false on non-Windows platforms +func isWindowsService() (bool, error) { + return false, nil +} diff --git a/cns/service/service_windows.go b/cns/service/service_windows.go new file mode 100644 index 0000000000..b6bdb464a6 --- /dev/null +++ b/cns/service/service_windows.go @@ -0,0 +1,219 @@ +//go:build windows +// +build windows + +// Copyright 2017 Microsoft. All rights reserved. +// MIT License + +package main + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" +) + +const ( + serviceName = "azure-cns" + serviceDisplayName = "Azure Container Networking Service" + serviceDescription = "Provides container networking services for Azure" +) + +// windowsService implements the svc.Handler interface for Windows service control +type windowsService struct { + runService func() +} + +// Execute is called by the Windows service manager and implements the service control loop +func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown + + changes <- svc.Status{State: svc.StartPending} + + // Start the service in a goroutine + go ws.runService() + + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + + // Service control loop +loop: + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + changes <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + changes <- svc.Status{State: svc.StopPending} + // Cancel the root context to signal shutdown + if rootCtx != nil { + // Send shutdown signal through the error channel + select { + case rootErrCh <- fmt.Errorf("service stop requested"): + default: + } + } + break loop + default: + // Log unexpected control request + } + } + } + + return +} + +// runAsService runs the application as a Windows service +func runAsService() error { + elog, err := eventlog.Open(serviceName) + if err != nil { + return fmt.Errorf("failed to open event log: %w", err) + } + defer elog.Close() + + elog.Info(1, fmt.Sprintf("Starting %s service", serviceName)) + + ws := &windowsService{ + runService: func() { + // The main service logic will run in the existing main() function + // after runAsService() returns + }, + } + + err = svc.Run(serviceName, ws) + if err != nil { + elog.Error(1, fmt.Sprintf("Service failed: %v", err)) + return fmt.Errorf("failed to run service: %w", err) + } + + elog.Info(1, fmt.Sprintf("%s service stopped", serviceName)) + return nil +} + +// installService installs the CNS as a Windows service +func installService() error { + exepath, err := getExecutablePath() + if err != nil { + return fmt.Errorf("failed to get executable path: %w", err) + } + + m, err := mgr.Connect() + if err != nil { + return fmt.Errorf("failed to connect to service manager: %w", err) + } + defer m.Disconnect() + + s, err := m.OpenService(serviceName) + if err == nil { + s.Close() + return fmt.Errorf("service %s already exists", serviceName) + } + + s, err = m.CreateService(serviceName, exepath, mgr.Config{ + DisplayName: serviceDisplayName, + Description: serviceDescription, + StartType: mgr.StartAutomatic, + ServiceStartName: "LocalSystem", + }) + if err != nil { + return fmt.Errorf("failed to create service: %w", err) + } + defer s.Close() + + // Set recovery options to restart the service on failure + err = s.SetRecoveryActions([]mgr.RecoveryAction{ + {Type: mgr.ServiceRestart, Delay: 5 * time.Second}, + {Type: mgr.ServiceRestart, Delay: 5 * time.Second}, + {Type: mgr.ServiceRestart, Delay: 5 * time.Second}, + }, 86400) // Reset failure count after 24 hours + if err != nil { + // This is not a fatal error, just log it + fmt.Printf("Warning: failed to set recovery actions: %v\n", err) + } + + // Set up event log + err = eventlog.InstallAsEventCreate(serviceName, eventlog.Error|eventlog.Warning|eventlog.Info) + if err != nil { + // Remove the service if we can't set up event log + s.Delete() + return fmt.Errorf("failed to setup event log: %w", err) + } + + fmt.Printf("Service %s installed successfully.\n", serviceName) + fmt.Printf("Run 'net start %s' to start the service.\n", serviceName) + return nil +} + +// uninstallService removes the CNS Windows service +func uninstallService() error { + m, err := mgr.Connect() + if err != nil { + return fmt.Errorf("failed to connect to service manager: %w", err) + } + defer m.Disconnect() + + s, err := m.OpenService(serviceName) + if err != nil { + return fmt.Errorf("service %s is not installed: %w", serviceName, err) + } + defer s.Close() + + // Try to stop the service if it's running + status, err := s.Query() + if err != nil { + return fmt.Errorf("failed to query service status: %w", err) + } + + if status.State != svc.Stopped { + status, err = s.Control(svc.Stop) + if err != nil { + return fmt.Errorf("failed to stop service: %w", err) + } + + // Wait for the service to stop + timeout := time.Now().Add(10 * time.Second) + for status.State != svc.Stopped { + if time.Now().After(timeout) { + return fmt.Errorf("timeout waiting for service to stop") + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("failed to query service status: %w", err) + } + } + } + + err = s.Delete() + if err != nil { + return fmt.Errorf("failed to delete service: %w", err) + } + + // Remove event log + err = eventlog.Remove(serviceName) + if err != nil { + // This is not fatal, just log it + fmt.Printf("Warning: failed to remove event log: %v\n", err) + } + + fmt.Printf("Service %s uninstalled successfully.\n", serviceName) + return nil +} + +// getExecutablePath returns the full path to the current executable +func getExecutablePath() (string, error) { + exepath, err := os.Executable() + if err != nil { + return "", err + } + return filepath.Abs(exepath) +} + +// isWindowsService checks if the application is running as a Windows service +func isWindowsService() (bool, error) { + return svc.IsWindowsService() +} diff --git a/common/config.go b/common/config.go index 3434c2e2e1..6ccb1e3e5d 100644 --- a/common/config.go +++ b/common/config.go @@ -145,4 +145,11 @@ const ( OptCNIConflistScenario = "cni-conflist-scenario" // OptCNIConflistScenarioAlias "shorthand" for the cni conflist scenairo, see above OptCNIConflistScenarioAlias = "cniconflistscenario" + + // Windows service management + OptServiceAction = "service" + OptServiceActionAlias = "s" + OptServiceInstall = "install" + OptServiceUninstall = "uninstall" + OptServiceRun = "run" ) From 405fa9d608f7f010b84eac5e44919c23f3157c9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:58:39 +0000 Subject: [PATCH 3/4] Add documentation for Windows service support - Add comprehensive Windows service documentation (cns/doc/windows-service.md) - Create CNS README with Windows service information - Include installation, configuration, and troubleshooting guides - Add command-line reference and examples Co-authored-by: rbtr <2940321+rbtr@users.noreply.github.com> --- cns/README.md | 128 ++++++++++++++++++++++++++ cns/doc/windows-service.md | 182 +++++++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 cns/README.md create mode 100644 cns/doc/windows-service.md diff --git a/cns/README.md b/cns/README.md new file mode 100644 index 0000000000..f807ba47f7 --- /dev/null +++ b/cns/README.md @@ -0,0 +1,128 @@ +# Azure Container Networking Service (CNS) + +Azure Container Networking Service (CNS) is a service that provides container networking capabilities for Azure environments. It manages IP address allocation, network policy enforcement, and container network configuration. + +## Features + +- **IP Address Management (IPAM)**: Dynamic allocation and management of IP addresses for containers +- **Network Container Management**: Creation and management of network containers +- **Multi-tenancy Support**: Isolation and management of network resources across multiple tenants +- **Kubernetes Integration**: Native integration with Kubernetes for CNI plugin support +- **Windows Service Support**: Run CNS as a Windows service for automatic startup and recovery + +## Running CNS + +### Linux + +On Linux systems, CNS is typically run as a daemon or managed by systemd: + +```bash +./azure-cns [OPTIONS] +``` + +### Windows + +On Windows systems, CNS can be run as a standalone executable or registered as a Windows service. + +#### As a Windows Service + +CNS can be installed as a Windows service to enable automatic startup and recovery: + +```powershell +# Install the service +azure-cns.exe --service install + +# Start the service +net start azure-cns +``` + +See [Windows Service Documentation](doc/windows-service.md) for detailed information on Windows service features and management. + +#### As a Standalone Executable + +You can also run CNS directly: + +```powershell +azure-cns.exe [OPTIONS] +``` + +## Configuration + +CNS can be configured using: +- Command-line arguments +- Configuration file (JSON format) +- Environment variables + +### Common Command-Line Options + +``` + -e, --environment= Set the operating environment {azure,mas,fileIpam} + -l, --log-level=info Set the logging level {info,debug} + -t, --log-target=logfile Set the logging target {syslog,stderr,logfile,stdout,stdoutfile} + -c, --cns-url= Set the URL for CNS to listen on + -p, --cns-port= Set the URL port for CNS to listen on + -cp, --config-path= Path to cns config file + -v, --version Print version information + -s, --service= Windows service action: install, uninstall, or run as service (Windows only) +``` + +For a complete list of options, run: + +```bash +azure-cns --help +``` + +## Building CNS + +To build CNS from source: + +```bash +cd cns/service +go build -o azure-cns +``` + +Or use the Makefile from the repository root: + +```bash +make azure-cns-binary +``` + +## Documentation + +- [Windows Service Documentation](doc/windows-service.md) - Detailed guide for running CNS as a Windows service +- [Swift V2 Features](../docs/feature/swift-v2/cns.md) - Swift V2 networking features +- [Async Delete](../docs/feature/async-delete/cns.md) - Asynchronous pod deletion + +## API Documentation + +CNS exposes a REST API for container network management. The API documentation can be found in the [swagger.yaml](swagger.yaml) file. + +## Development + +### Testing + +Run tests using: + +```bash +cd cns +go test ./... +``` + +### Linting + +Format and lint the code: + +```bash +make fmt +make lint +``` + +## Support + +For issues and questions: +- Create an issue in the [GitHub repository](https://github.com/Azure/azure-container-networking/issues) +- Review existing [documentation](../docs/) + +## License + +This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details. diff --git a/cns/doc/windows-service.md b/cns/doc/windows-service.md new file mode 100644 index 0000000000..cadc5b64c5 --- /dev/null +++ b/cns/doc/windows-service.md @@ -0,0 +1,182 @@ +# Azure CNS Windows Service + +Azure CNS (Container Networking Service) can be registered and run as a Windows service, enabling automatic startup on system boot and automatic restart on failure. + +## Features + +- **Automatic Startup**: CNS starts automatically when the Windows system boots +- **Automatic Recovery**: Service automatically restarts on failure (with 5-second delays) +- **Event Logging**: Service lifecycle events are logged to Windows Event Log +- **Service Management**: Easy installation and uninstallation via command-line flags + +## Installation + +To install Azure CNS as a Windows service, run the following command with administrator privileges: + +```powershell +azure-cns.exe --service install +``` + +or using the short form: + +```powershell +azure-cns.exe -s install +``` + +This will: +1. Register the service with the Windows Service Control Manager +2. Configure the service to start automatically on system boot +3. Set up automatic restart on failure +4. Create an event log source for CNS + +After installation, you can start the service using: + +```powershell +net start azure-cns +``` + +or via the Services management console (`services.msc`). + +## Uninstallation + +To uninstall the Azure CNS Windows service, run the following command with administrator privileges: + +```powershell +azure-cns.exe --service uninstall +``` + +or using the short form: + +```powershell +azure-cns.exe -s uninstall +``` + +This will: +1. Stop the service if it's running +2. Remove the service from the Windows Service Control Manager +3. Remove the event log source + +## Service Configuration + +The service is registered with the following configuration: + +- **Service Name**: `azure-cns` +- **Display Name**: `Azure Container Networking Service` +- **Description**: `Provides container networking services for Azure` +- **Start Type**: Automatic +- **Service Account**: LocalSystem +- **Recovery Actions**: + - First failure: Restart the service after 5 seconds + - Second failure: Restart the service after 5 seconds + - Subsequent failures: Restart the service after 5 seconds + - Reset failure count after: 24 hours + +## Running as a Service + +Once installed, the service will automatically detect when it's being started by the Windows Service Control Manager and will run in service mode. No additional command-line flags are needed when the service is started by Windows. + +For testing purposes, you can explicitly run in service mode using: + +```powershell +azure-cns.exe --service run +``` + +## Event Logging + +Service lifecycle events are logged to the Windows Event Log under the Application log with the source name `azure-cns`. You can view these logs using: + +```powershell +Get-EventLog -LogName Application -Source azure-cns -Newest 20 +``` + +or via the Event Viewer (`eventvwr.msc`). + +## Troubleshooting + +### Service fails to install + +Ensure you are running the command prompt or PowerShell as Administrator. The service installation requires elevated privileges. + +### Service fails to start + +1. Check the Windows Event Log for error messages: + ```powershell + Get-EventLog -LogName Application -Source azure-cns -Newest 10 + ``` + +2. Verify that all required configuration files are present + +3. Check that the executable path is correct + +4. Try running the executable directly (not as a service) to identify any configuration issues: + ```powershell + azure-cns.exe + ``` + +### Service doesn't restart on failure + +The service is configured to restart automatically up to 3 times within a 24-hour period. If the service continues to fail, it will remain stopped. Check the Event Log for the root cause and fix the underlying issue before restarting the service. + +## Command-Line Reference + +``` + -s, --service= Windows service action: install, uninstall, or run as service {install,uninstall,run,} +``` + +**Available actions:** +- `install`: Install Azure CNS as a Windows service +- `uninstall`: Uninstall the Azure CNS Windows service +- `run`: Explicitly run in service mode (typically not needed) +- ` ` (empty): Normal execution mode (auto-detects if running as service) + +## Examples + +### Install and start the service + +```powershell +# Install the service +azure-cns.exe --service install + +# Start the service +net start azure-cns + +# Verify the service is running +Get-Service azure-cns +``` + +### Stop and uninstall the service + +```powershell +# Stop the service +net stop azure-cns + +# Uninstall the service +azure-cns.exe --service uninstall +``` + +### Check service status + +```powershell +# Using PowerShell +Get-Service azure-cns + +# Using sc.exe +sc query azure-cns +``` + +### View service logs + +```powershell +# View recent events +Get-EventLog -LogName Application -Source azure-cns -Newest 20 | Format-Table -AutoSize + +# View only errors +Get-EventLog -LogName Application -Source azure-cns -EntryType Error -Newest 10 +``` + +## Notes + +- This feature is only available on Windows platforms. On Linux, use systemd or other init systems to manage the service. +- The service must be installed with administrator privileges. +- The service runs under the LocalSystem account, which has high privileges. Ensure the executable and configuration files are properly secured. +- Service configuration (command-line flags, config files) should be set via the Windows Registry or by using command-line parameters in the service's "Image Path" in the service properties. From acfdd7ebf9d81a335ca6cb0d93269a96c48d214c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:00:03 +0000 Subject: [PATCH 4/4] Add tests for Windows service functionality - Add unit tests for service functions on non-Windows platforms - Verify proper error handling when service functions are called on Linux - Ensure isWindowsService returns false on non-Windows platforms Co-authored-by: rbtr <2940321+rbtr@users.noreply.github.com> --- cns/service/service_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 cns/service/service_test.go diff --git a/cns/service/service_test.go b/cns/service/service_test.go new file mode 100644 index 0000000000..0e99789238 --- /dev/null +++ b/cns/service/service_test.go @@ -0,0 +1,37 @@ +//go:build !windows +// +build !windows + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestServiceFunctionsOnNonWindows tests that service functions return appropriate errors on non-Windows platforms +func TestServiceFunctionsOnNonWindows(t *testing.T) { + t.Run("installService should fail on non-Windows", func(t *testing.T) { + err := installService() + assert.Error(t, err) + assert.Contains(t, err.Error(), "only supported on Windows") + }) + + t.Run("uninstallService should fail on non-Windows", func(t *testing.T) { + err := uninstallService() + assert.Error(t, err) + assert.Contains(t, err.Error(), "only supported on Windows") + }) + + t.Run("runAsService should fail on non-Windows", func(t *testing.T) { + err := runAsService() + assert.Error(t, err) + assert.Contains(t, err.Error(), "only supported on Windows") + }) + + t.Run("isWindowsService should return false on non-Windows", func(t *testing.T) { + isService, err := isWindowsService() + assert.NoError(t, err) + assert.False(t, isService) + }) +}