diff --git a/aks-node-controller/app.go b/aks-node-controller/app.go index 29e3fbdaa73..6f26e72a9a2 100644 --- a/aks-node-controller/app.go +++ b/aks-node-controller/app.go @@ -168,8 +168,8 @@ func (a *App) Run(ctx context.Context, args []string) int { Usage: "Wait for provisioning to complete", Action: func(ctx context.Context, cmd *cli.Command) error { provisionStatusFiles := ProvisionStatusFiles{ - ProvisionJSONFile: provisionJSONFilePath, - ProvisionCompleteFile: provisionCompleteFilePath, + ProvisionJSONFile: osCfg.provisionJSONFilePath, + ProvisionCompleteFile: osCfg.provisionCompleteFilePath, } provisionOutput, err := a.runProvisionWaitCommand(ctx, provisionStatusFiles) _, _ = fmt.Fprintln(cmd.Root().Writer, provisionOutput) @@ -721,7 +721,10 @@ func (a *App) Provision(ctx context.Context, flags ProvisionFlags) (*ProvisionRe func (a *App) reportProvisionStatus(ctx context.Context, result *ProvisionResult, provisionErr error) { markerPath := a.readyReportMarkerPath if markerPath == "" { - markerPath = readyReportMarkerPath + markerPath = osCfg.readyReportMarkerPath + } + if markerPath == "" { + return } if _, err := os.Stat(markerPath); err != nil { if !errors.Is(err, os.ErrNotExist) { @@ -790,27 +793,27 @@ func (a *App) writeCompleteFileOnError(provisionResult *ProvisionResult, err err if err == nil { return } - if _, statErr := os.Stat(provisionJSONFilePath); statErr != nil && errors.Is(statErr, os.ErrNotExist) { + if _, statErr := os.Stat(osCfg.provisionJSONFilePath); statErr != nil && errors.Is(statErr, os.ErrNotExist) { data, err := json.Marshal(provisionResult) if err != nil { slog.Error("failed to marshal provision result", "error", err) } - baseDir := filepath.Dir(provisionJSONFilePath) + baseDir := filepath.Dir(osCfg.provisionJSONFilePath) if writeErr := os.MkdirAll(baseDir, 0755); writeErr != nil { slog.Error("failed to create directory for provision.json file", "path", baseDir, "error", writeErr) } - if writeErr := os.WriteFile(provisionJSONFilePath, data, 0600); writeErr != nil { - slog.Error("failed to write provision.json file", "path", provisionJSONFilePath, "error", writeErr) + if writeErr := os.WriteFile(osCfg.provisionJSONFilePath, data, 0600); writeErr != nil { + slog.Error("failed to write provision.json file", "path", osCfg.provisionJSONFilePath, "error", writeErr) } } - if _, statErr := os.Stat(provisionCompleteFilePath); statErr == nil { + if _, statErr := os.Stat(osCfg.provisionCompleteFilePath); statErr == nil { return // already exists } else if !errors.Is(statErr, os.ErrNotExist) { // unexpected error - slog.Error("failed to stat provision.complete file", "path", provisionCompleteFilePath, "error", statErr) + slog.Error("failed to stat provision.complete file", "path", osCfg.provisionCompleteFilePath, "error", statErr) return } - if writeErr := os.WriteFile(provisionCompleteFilePath, []byte{}, 0600); writeErr != nil { - slog.Error("failed to write provision.complete file", "path", provisionCompleteFilePath, "error", writeErr) + if writeErr := os.WriteFile(osCfg.provisionCompleteFilePath, []byte{}, 0600); writeErr != nil { + slog.Error("failed to write provision.complete file", "path", osCfg.provisionCompleteFilePath, "error", writeErr) } } diff --git a/aks-node-controller/const.go b/aks-node-controller/const.go index 3508cb2f150..6a668d32882 100644 --- a/aks-node-controller/const.go +++ b/aks-node-controller/const.go @@ -1,10 +1,43 @@ package main -// Some options are intentionally non-configurable to avoid customization by users -// it will help us to avoid introducing any breaking changes in the future. -const ( - logPath = "/var/log/azure/aks-node-controller.log" - provisionJSONFilePath = "/var/log/azure/aks/provision.json" - provisionCompleteFilePath = "/opt/azure/containers/provision.complete" - readyReportMarkerPath = "/var/lib/waagent/experimental_skip_ready_report" -) +import "runtime" + +// Holds the OS-specific locations. Some options are intentionally non-configurable +// to avoid customization by users; it will help us avoid introducing breaking changes in the future. +type osConfig struct { + logPath string + provisionJSONFilePath string + provisionCompleteFilePath string + readyReportMarkerPath string + eventsDirPath string +} + +func linuxConfig() osConfig { + return osConfig{ + logPath: "/var/log/azure/aks-node-controller.log", + provisionJSONFilePath: "/var/log/azure/aks/provision.json", + provisionCompleteFilePath: "/opt/azure/containers/provision.complete", + readyReportMarkerPath: "/var/lib/waagent/experimental_skip_ready_report", + eventsDirPath: "/var/log/azure/Microsoft.Azure.Extensions.CustomScript/events", + } +} + +func windowsConfig() osConfig { + return osConfig{ + logPath: `C:\AzureData\aks-node-controller\aks-node-controller.log`, + provisionJSONFilePath: `C:\AzureData\aks-node-controller\provision.json`, + provisionCompleteFilePath: `C:\AzureData\aks-node-controller\provision.complete`, + // The Ready-report handoff is a Linux waagent feature. + readyReportMarkerPath: "", + eventsDirPath: `C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\Events\`, + } +} + +func configForOS(goos string) osConfig { + if goos == "windows" { + return windowsConfig() + } + return linuxConfig() +} + +var osCfg = configForOS(runtime.GOOS) //nolint:gochecknoglobals // read-only OS-specific paths resolved once at startup diff --git a/aks-node-controller/const_test.go b/aks-node-controller/const_test.go new file mode 100644 index 00000000000..8a9c46a8950 --- /dev/null +++ b/aks-node-controller/const_test.go @@ -0,0 +1,15 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConfigForOS(t *testing.T) { + require.Equal(t, windowsConfig(), configForOS("windows")) + require.Equal(t, linuxConfig(), configForOS("linux")) + require.Equal(t, linuxConfig(), configForOS("darwin")) + require.Empty(t, windowsConfig().readyReportMarkerPath) + require.NotEmpty(t, linuxConfig().readyReportMarkerPath) +} diff --git a/aks-node-controller/filelock_darwin.go b/aks-node-controller/filelock_darwin.go new file mode 100644 index 00000000000..759b474e9d3 --- /dev/null +++ b/aks-node-controller/filelock_darwin.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + "syscall" +) + +// lockFileExclusive blocks until it holds an exclusive lock on f. Closing f releases it. +func lockFileExclusive(f *os.File) error { + return syscall.Flock(int(f.Fd()), syscall.LOCK_EX) +} diff --git a/aks-node-controller/filelock_linux.go b/aks-node-controller/filelock_linux.go new file mode 100644 index 00000000000..759b474e9d3 --- /dev/null +++ b/aks-node-controller/filelock_linux.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + "syscall" +) + +// lockFileExclusive blocks until it holds an exclusive lock on f. Closing f releases it. +func lockFileExclusive(f *os.File) error { + return syscall.Flock(int(f.Fd()), syscall.LOCK_EX) +} diff --git a/aks-node-controller/filelock_windows.go b/aks-node-controller/filelock_windows.go new file mode 100644 index 00000000000..626108f22e8 --- /dev/null +++ b/aks-node-controller/filelock_windows.go @@ -0,0 +1,12 @@ +package main + +import ( + "errors" + "os" +) + +// lockFileExclusive is unsupported on Windows: its only caller writes the Linux Hyper-V +// KVP pool file, and Windows exposes KVP through the registry rather than a file. +func lockFileExclusive(*os.File) error { + return errors.ErrUnsupported +} diff --git a/aks-node-controller/goal_state.go b/aks-node-controller/goal_state.go index f0b7530bb90..a9a28e415d4 100644 --- a/aks-node-controller/goal_state.go +++ b/aks-node-controller/goal_state.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "strings" - "syscall" "time" "unicode/utf8" @@ -262,7 +261,7 @@ func writeKVPRecord(path, key, value string) error { } defer f.Close() // Closing the file releases the lock; os.File.Write is unbuffered. - if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { + if err = lockFileExclusive(f); err != nil { return err } _, err = f.Write(record) diff --git a/aks-node-controller/main.go b/aks-node-controller/main.go index be34e75f27c..4b96b978b4e 100644 --- a/aks-node-controller/main.go +++ b/aks-node-controller/main.go @@ -16,7 +16,7 @@ func main() { logCleanup := configureLogging() app := App{ cmdRun: cmdRunner, - eventLogger: helpers.NewEventLogger("/var/log/azure/Microsoft.Azure.Extensions.CustomScript/events"), + eventLogger: helpers.NewEventLogger(osCfg.eventsDirPath), } exitCode := app.Run(context.Background(), os.Args) @@ -54,8 +54,8 @@ func configureLogging() func() { func setupLogPath() string { // Try to create production directory first - if err := os.MkdirAll(filepath.Dir(logPath), 0755); err == nil { - return logPath + if err := os.MkdirAll(filepath.Dir(osCfg.logPath), 0755); err == nil { + return osCfg.logPath } // If directory creation fails, fallback to current directory return "aks-node-controller.log"