diff --git a/integration-test/env/Extension/HandlerEnvironment.json b/integration-test/env/Extension/HandlerEnvironment.json index ac46f47..05bddb1 100644 --- a/integration-test/env/Extension/HandlerEnvironment.json +++ b/integration-test/env/Extension/HandlerEnvironment.json @@ -7,7 +7,9 @@ "logFolder": "/var/log/azure/Extension", "configFolder": "/var/lib/waagent/Extension/config", "statusFolder": "/var/lib/waagent/Extension/status", - "heartbeatFile": "/var/lib/waagent/Extension/heartbeat.log" + "heartbeatFile": "/var/lib/waagent/Extension/heartbeat.log", + "eventsFolder": "/var/log/azure/Extension/events", + "eventsFolder_preview": "/var/log/azure/Extension/events" } } ] \ No newline at end of file diff --git a/integration-test/test/7_vmwatch.bats b/integration-test/test/7_vmwatch.bats index 53c3b34..8cf873c 100644 --- a/integration-test/test/7_vmwatch.bats +++ b/integration-test/test/7_vmwatch.bats @@ -95,7 +95,7 @@ teardown(){ [[ "$output" == *'VMWatch process started'* ]] [[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]] [[ "$output" == *'--input-filter disk_io:outbound_connectivity:clockskew:az_storage_blob'* ]] - [[ "$output" == *'Env: [SIGNAL_FOLDER=/var/log/azure/Microsoft.ManagedServices.ApplicationHealthLinux/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] + [[ "$output" == *'Env: [SIGNAL_FOLDER=/var/log/azure/Extension/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] [[ "$output" == *'VMWatch is running'* ]] status_file="$(container_read_extension_status)" @@ -131,7 +131,7 @@ teardown(){ [[ "$output" == *'VMWatch process started'* ]] [[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]] [[ "$output" == *'--input-filter disk_io:outbound_connectivity'* ]] - [[ "$output" == *'Env: [ABC=abc BCD=bcd SIGNAL_FOLDER=/var/log/azure/Microsoft.ManagedServices.ApplicationHealthLinux/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] + [[ "$output" == *'Env: [ABC=abc BCD=bcd SIGNAL_FOLDER=/var/log/azure/Extension/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] [[ "$output" == *'VMWatch is running'* ]] status_file="$(container_read_extension_status)" @@ -174,7 +174,7 @@ teardown(){ [[ "$output" == *'VMWatch process started'* ]] [[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]] [[ "$output" == *'--input-filter disk_io:outbound_connectivity'* ]] - [[ "$output" == *'Env: [SIGNAL_FOLDER=/var/log/azure/Microsoft.ManagedServices.ApplicationHealthLinux/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] + [[ "$output" == *'Env: [SIGNAL_FOLDER=/var/log/azure/Extension/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/vmwatch.log]'* ]] [[ "$output" == *'VMWatch is running'* ]] status_file="$(container_read_extension_status)" diff --git a/main/cmds.go b/main/cmds.go index b71aab4..1986487 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -2,15 +2,15 @@ package main import ( "fmt" - "github.com/Azure/azure-docker-extension/pkg/vmextension" - "github.com/go-kit/kit/log" - "github.com/pkg/errors" "os" "strings" "time" + + "github.com/go-kit/kit/log" + "github.com/pkg/errors" ) -type cmdFunc func(ctx *log.Context, hEnv vmextension.HandlerEnvironment, seqNum int) (msg string, err error) +type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, err error) type preFunc func(ctx *log.Context, seqNum int) error type cmd struct { @@ -39,12 +39,12 @@ var ( } ) -func noop(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) { +func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { ctx.Log("event", "noop") return "", nil } -func install(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) { +func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { if err := os.MkdirAll(dataDir, 0755); err != nil { return "", errors.Wrap(err, "failed to create data dir") } @@ -54,7 +54,7 @@ func install(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (st return "", nil } -func uninstall(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) { +func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { { // a new context scope with path ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) @@ -75,7 +75,7 @@ var ( errTerminated = errors.New("Application health process terminated") ) -func enable(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) { +func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { // parse the extension handler settings (not available prior to 'enable') cfg, err := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder) if err != nil { diff --git a/main/constants.go b/main/constants.go index 593247f..8f7a2a2 100644 --- a/main/constants.go +++ b/main/constants.go @@ -16,7 +16,6 @@ const ( // and it also doesn't have the latest properties like EventsFolder. Importing a separate package // is possible, but may result in lots of code churn. We will temporarily keep this as a constant since the // events folder is unlikely to change in the future. - HandlerEnvironmentEventsFolderPath = "/var/log/azure/Microsoft.ManagedServices.ApplicationHealthLinux/events" VMWatchBinaryNameAmd64 = "vmwatch_linux_amd64" VMWatchBinaryNameArm64 = "vmwatch_linux_arm64" diff --git a/main/handlerenv.go b/main/handlerenv.go new file mode 100644 index 0000000..3fd278f --- /dev/null +++ b/main/handlerenv.go @@ -0,0 +1,84 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// HandlerEnvFileName is the file name of the Handler Environment as placed by the +// Azure Linux Guest Agent. +const HandlerEnvFileName = "HandlerEnvironment.json" + +// HandlerEnvironment describes the handler environment configuration presented +// to the extension handler by the Azure Linux Guest Agent. +type HandlerEnvironment struct { + Version float64 `json:"version"` + Name string `json:"name"` + HandlerEnvironment struct { + HeartbeatFile string `json:"heartbeatFile"` + StatusFolder string `json:"statusFolder"` + ConfigFolder string `json:"configFolder"` + LogFolder string `json:"logFolder"` + EventsFolder string `json:"eventsFolder"` + EventsFolderPreview string `json:"eventsFolder_preview"` + DeploymentID string `json:"deploymentid"` + RoleName string `json:"rolename"` + Instance string `json:"instance"` + HostResolverAddress string `json:"hostResolverAddress"` + } +} + +// GetHandlerEnv locates the HandlerEnvironment.json file by assuming it lives +// next to or one level above the extension handler (read: this) executable, +// reads, parses and returns it. +func GetHandlerEnv() (he HandlerEnvironment, _ error) { + dir, err := scriptDir() + if err != nil { + return he, fmt.Errorf("vmextension: cannot find base directory of the running process: %v", err) + } + paths := []string{ + filepath.Join(dir, HandlerEnvFileName), // this level (i.e. executable is in [EXT_NAME]/.) + filepath.Join(dir, "..", HandlerEnvFileName), // one up (i.e. executable is in [EXT_NAME]/bin/.) + } + var b []byte + for _, p := range paths { + o, err := ioutil.ReadFile(p) + if err != nil && !os.IsNotExist(err) { + return he, fmt.Errorf("vmextension: error examining HandlerEnvironment at '%s': %v", p, err) + } else if err == nil { + b = o + break + } + } + if b == nil { + return he, fmt.Errorf("vmextension: Cannot find HandlerEnvironment at paths: %s", strings.Join(paths, ", ")) + } + return ParseHandlerEnv(b) +} + +// scriptDir returns the absolute path of the running process. +func scriptDir() (string, error) { + p, err := filepath.Abs(os.Args[0]) + if err != nil { + return "", err + } + return filepath.Dir(p), nil +} + +// ParseHandlerEnv parses the +// /var/lib/waagent/[extension]/HandlerEnvironment.json format. +func ParseHandlerEnv(b []byte) (he HandlerEnvironment, _ error) { + var hf []HandlerEnvironment + + if err := json.Unmarshal(b, &hf); err != nil { + return he, fmt.Errorf("vmextension: failed to parse handler env: %v", err) + } + if len(hf) != 1 { + return he, fmt.Errorf("vmextension: expected 1 config in parsed HandlerEnvironment, found: %v", len(hf)) + } + return hf[0], nil +} diff --git a/main/main.go b/main/main.go index 16d2377..7368061 100644 --- a/main/main.go +++ b/main/main.go @@ -8,7 +8,6 @@ import ( "strings" "syscall" - "github.com/Azure/azure-docker-extension/pkg/vmextension" "github.com/go-kit/kit/log" ) @@ -42,12 +41,12 @@ func main() { }() // parse extension environment - hEnv, err := vmextension.GetHandlerEnv() + hEnv, err := GetHandlerEnv() if err != nil { ctx.Log("message", "failed to parse handlerenv", "error", err) os.Exit(cmd.failExitCode) } - seqNum, err := vmextension.FindSeqNum(hEnv.HandlerEnvironment.ConfigFolder) + seqNum, err := FindSeqNum(hEnv.HandlerEnvironment.ConfigFolder) if err != nil { ctx.Log("messsage", "failed to find sequence number", "error", err) } diff --git a/main/reportstatus.go b/main/reportstatus.go index ec5e77e..71808cb 100644 --- a/main/reportstatus.go +++ b/main/reportstatus.go @@ -1,7 +1,6 @@ package main import ( - "github.com/Azure/azure-docker-extension/pkg/vmextension" "github.com/go-kit/kit/log" "github.com/pkg/errors" ) @@ -11,7 +10,7 @@ import ( // status. // // If an error occurs reporting the status, it will be logged and returned. -func reportStatus(ctx *log.Context, hEnv vmextension.HandlerEnvironment, seqNum int, t StatusType, c cmd, msg string) error { +func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t StatusType, c cmd, msg string) error { if !c.shouldReportStatus { ctx.Log("status", "not reported for operation (by design)") return nil @@ -24,7 +23,7 @@ func reportStatus(ctx *log.Context, hEnv vmextension.HandlerEnvironment, seqNum return nil } -func reportStatusWithSubstatuses(ctx *log.Context, hEnv vmextension.HandlerEnvironment, seqNum int, t StatusType, op string, msg string, substatuses []SubstatusItem) error { +func reportStatusWithSubstatuses(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t StatusType, op string, msg string, substatuses []SubstatusItem) error { s := NewStatus(t, op, msg) for _, substatus := range substatuses { s.AddSubstatusItem(substatus) diff --git a/main/reportstatus_test.go b/main/reportstatus_test.go index a7973e4..770180d 100644 --- a/main/reportstatus_test.go +++ b/main/reportstatus_test.go @@ -6,7 +6,6 @@ import ( "path/filepath" "testing" - "github.com/Azure/azure-docker-extension/pkg/vmextension" "github.com/go-kit/kit/log" "github.com/stretchr/testify/require" ) @@ -23,7 +22,7 @@ func Test_statusMsg(t *testing.T) { } func Test_reportStatus_fails(t *testing.T) { - fakeEnv := vmextension.HandlerEnvironment{} + fakeEnv := HandlerEnvironment{} fakeEnv.HandlerEnvironment.StatusFolder = "/non-existing/dir/" err := reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusSuccess, cmdEnable, "") @@ -36,7 +35,7 @@ func Test_reportStatus_fileExists(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(tmpDir) - fakeEnv := vmextension.HandlerEnvironment{} + fakeEnv := HandlerEnvironment{} fakeEnv.HandlerEnvironment.StatusFolder = tmpDir require.Nil(t, reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, "FOO ERROR")) @@ -53,7 +52,7 @@ func Test_reportStatus_checksIfShouldBeReported(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(tmpDir) - fakeEnv := vmextension.HandlerEnvironment{} + fakeEnv := HandlerEnvironment{} fakeEnv.HandlerEnvironment.StatusFolder = tmpDir require.Nil(t, reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 2, StatusSuccess, c, "")) diff --git a/main/seqnum.go b/main/seqnum.go new file mode 100644 index 0000000..8b2d646 --- /dev/null +++ b/main/seqnum.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" +) + +// FindSeqnum finds the file with the highest number under configFolder +// named like 0.settings, 1.settings so on. +func FindSeqNum(configFolder string) (int, error) { + g, err := filepath.Glob(configFolder + "/*.settings") + if err != nil { + return 0, err + } + seqs := make([]int, len(g)) + for _, v := range g { + f := filepath.Base(v) + i, err := strconv.Atoi(strings.Replace(f, ".settings", "", 1)) + if err != nil { + return 0, fmt.Errorf("Can't parse int from filename: %s", f) + } + seqs = append(seqs, i) + } + if len(seqs) == 0 { + return 0, fmt.Errorf("Can't find out seqnum from %s, not enough files.", configFolder) + } + sort.Sort(sort.Reverse(sort.IntSlice(seqs))) + return seqs[0], nil +} diff --git a/main/vmWatch.go b/main/vmWatch.go index 6f065e6..7a5dda2 100644 --- a/main/vmWatch.go +++ b/main/vmWatch.go @@ -3,13 +3,13 @@ package main import ( "bytes" "fmt" - "github.com/Azure/azure-docker-extension/pkg/vmextension" - "github.com/go-kit/kit/log" "os" "os/exec" "path/filepath" "strings" "time" + + "github.com/go-kit/kit/log" ) type VMWatchStatus string @@ -49,7 +49,7 @@ func (r *VMWatchResult) GetMessage() string { // We will setup and execute VMWatch as a separate process. Ideally VMWatch should run indefinitely, // but as a best effort we will attempt at most 3 times to run the process -func executeVMWatch(ctx *log.Context, s *vmWatchSettings, h vmextension.HandlerEnvironment, vmWatchResultChannel chan VMWatchResult) { +func executeVMWatch(ctx *log.Context, s *vmWatchSettings, hEnv HandlerEnvironment, vmWatchResultChannel chan VMWatchResult) { var vmWatchErr error defer func() { if r := recover(); r != nil { @@ -63,11 +63,11 @@ func executeVMWatch(ctx *log.Context, s *vmWatchSettings, h vmextension.HandlerE // Best effort to start VMWatch process each time it fails for i := 1; i <= VMWatchMaxProcessAttempts; i++ { - vmWatchErr = executeVMWatchHelper(ctx, i, s, h) + vmWatchErr = executeVMWatchHelper(ctx, i, s, hEnv) } } -func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatchSettings, handlerEnvironment vmextension.HandlerEnvironment) (err error) { +func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatchSettings, hEnv HandlerEnvironment) (err error) { pid := -1 var cmd *exec.Cmd defer func() { @@ -79,9 +79,9 @@ func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatc }() // Setup command - cmd, err = setupVMWatchCommand(vmWatchSettings, handlerEnvironment) + cmd, err = setupVMWatchCommand(vmWatchSettings, hEnv) if err != nil { - err = fmt.Errorf("[%v][PID -1] Attempt %d: VMWatch setup failed. Error: %w", time.Now().UTC().Format(time.RFC3339), pid, attempt, err) + err = fmt.Errorf("[%v][PID -1] Attempt %d: VMWatch setup failed. Error: %w", time.Now().UTC().Format(time.RFC3339), attempt, err) ctx.Log("error", err.Error()) return err } @@ -125,7 +125,7 @@ func killVMWatch(ctx *log.Context, cmd *exec.Cmd) error { return nil } -func setupVMWatchCommand(s *vmWatchSettings, h vmextension.HandlerEnvironment) (*exec.Cmd, error) { +func setupVMWatchCommand(s *vmWatchSettings, hEnv HandlerEnvironment) (*exec.Cmd, error) { processDirectory, err := GetProcessDirectory() if err != nil { return nil, err @@ -142,7 +142,7 @@ func setupVMWatchCommand(s *vmWatchSettings, h vmextension.HandlerEnvironment) ( cmd := exec.Command(GetVMWatchBinaryFullPath(processDirectory), args...) - cmd.Env = GetVMWatchEnvironmentVariables(s.ParameterOverrides, h) + cmd.Env = GetVMWatchEnvironmentVariables(s.ParameterOverrides, hEnv) return cmd, nil } @@ -169,14 +169,14 @@ func GetVMWatchBinaryFullPath(processDirectory string) string { return filepath.Join(processDirectory, "VMWatch", binaryName) } -func GetVMWatchEnvironmentVariables(parameterOverrides map[string]interface{}, h vmextension.HandlerEnvironment) []string { +func GetVMWatchEnvironmentVariables(parameterOverrides map[string]interface{}, hEnv HandlerEnvironment) []string { var arr []string for key, value := range parameterOverrides { arr = append(arr, fmt.Sprintf("%s=%s", key, value)) } - arr = append(arr, fmt.Sprintf("SIGNAL_FOLDER=%s", HandlerEnvironmentEventsFolderPath)) - arr = append(arr, fmt.Sprintf("VERBOSE_LOG_FILE_FULL_PATH=%s", filepath.Join(h.HandlerEnvironment.LogFolder, VMWatchVerboseLogFileName))) + arr = append(arr, fmt.Sprintf("SIGNAL_FOLDER=%s", hEnv.HandlerEnvironment.EventsFolder)) + arr = append(arr, fmt.Sprintf("VERBOSE_LOG_FILE_FULL_PATH=%s", filepath.Join(hEnv.HandlerEnvironment.LogFolder, VMWatchVerboseLogFileName))) return arr } diff --git a/misc/manifest.xml b/misc/manifest.xml index 5af845a..b34ac44 100644 --- a/misc/manifest.xml +++ b/misc/manifest.xml @@ -2,7 +2,7 @@ Microsoft.ManagedServices ApplicationHealthLinux - 2.0.7 + 2.0.8 VmRole