From f78d7040564bb5dd7f1346b5b37209e42c2b29b3 Mon Sep 17 00:00:00 2001 From: Gabriel Mougard Date: Wed, 23 Oct 2024 11:53:02 +0200 Subject: [PATCH] lxd/network/acl: Read OVN logs from systemd journal In the case of an OVN controller being deployed as part of a MicroOVN deployment, the OVN controller logs are stored in MicroOVN's snap syslog. The LXD snap should have root access, which means that it should be authorized (this is being tested) to read the OVN controller logs. In the case where logs couldn't be read (e.g, AppArmor restrictions), we could try to connect the `log-observe` snap interface on LXD and see if this works. Signed-off-by: Gabriel Mougard --- lxd/network/acl/acl_ovn.go | 100 +++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/lxd/network/acl/acl_ovn.go b/lxd/network/acl/acl_ovn.go index 0a3b19b22f7e..858dac127d3f 100644 --- a/lxd/network/acl/acl_ovn.go +++ b/lxd/network/acl/acl_ovn.go @@ -18,6 +18,8 @@ import ( "github.com/canonical/lxd/shared/logger" "github.com/canonical/lxd/shared/revert" "github.com/canonical/lxd/shared/validate" + "github.com/coreos/go-systemd/v22/dbus" + "github.com/coreos/go-systemd/v22/sdjournal" ) // OVN ACL rule priorities. @@ -1131,3 +1133,101 @@ func ovnParseLogEntry(input string, prefix string) string { return string(out) } + +func checkSystemDUnitStatus(unitName string) error { + ctx := context.Background() + conn, err := dbus.NewSystemConnectionContext(ctx) + if err != nil { + return fmt.Errorf("Failed to connect to systemd: %v", err) + } + + defer conn.Close() + unitStatus, err := conn.GetUnitPropertiesContext(ctx, unitName) + if err != nil { + return fmt.Errorf("Failed to get unit properties: %v", err) + } + + loadState, ok := unitStatus["LoadState"].(string) + if !ok || loadState != "loaded" { + return fmt.Errorf("Unit %q is not loaded (LoadState=%s)", unitName, loadState) + } + + activeState, ok := unitStatus["ActiveState"].(string) + if !ok || activeState != "active" { + return fmt.Errorf("Unit %q is not active (ActiveState=%s)", unitName, activeState) + } + + return nil +} + +// ovnParseLogEntriesFromSyslog +func ovnParseLogEntriesFromSyslog(l logger.Logger, systemdUnitName string, prefix string) ([]string, error) { + var logEntries []string + j, err := sdjournal.NewJournal() + if err != nil { + return nil, fmt.Errorf("Failed to open a Systemd journal instance: %v", err) + } + + defer j.Close() + + match := sdjournal.Match{ + Field: sdjournal.SD_JOURNAL_FIELD_SYSTEMD_UNIT, + Value: systemdUnitName, + } + err = j.AddMatch(match.String()) + if err != nil { + return nil, fmt.Errorf("Failed to add match: %v", err) + } + + err = j.SeekTail() + if err != nil { + return nil, fmt.Errorf("Failed to seek to journal tail: %v", err) + } + + _, err = j.Previous() + if err != nil { + return nil, fmt.Errorf("Failed to move to previous entry: %v", err) + } + + // Iterate over the journal entries backwards. + // For now, we output a maximum of 5000 entries. + count := 0 + maxCount := 5000 + for count < maxCount { + n, err := j.Previous() + if err != nil { + return nil, fmt.Errorf("Error reading journal: %v", err) + } + + if n == 0 { + break // No more entries + } + + entry, err := j.GetEntry() + if err != nil { + return nil, fmt.Errorf("Error getting journal entry: %v", err) + } + + message, ok := entry.Fields["MESSAGE"] + if !ok { + l.Error("GABRIEL 1 - No message field in journal entry", logger.Ctx{"entry": entry}) + continue + } + + logEntry := ovnParseLogEntry(message, prefix) + if logEntry == "" { + l.Error("GABRIEL 2 - log entry empty", logger.Ctx{"message": message}) + continue + } + + l.Error("GABRIEL 3 - log entry", logger.Ctx{"logEntry": logEntry}) + logEntries = append(logEntries, logEntry) + count++ + } + + for i, j := 0, len(logEntries)-1; i < j; i, j = i+1, j-1 { + logEntries[i], logEntries[j] = logEntries[j], logEntries[i] + } + + return logEntries, nil +}