diff --git a/lxd/network/acl/acl_ovn.go b/lxd/network/acl/acl_ovn.go index e5b846a351fc..f61f2d183c13 100644 --- a/lxd/network/acl/acl_ovn.go +++ b/lxd/network/acl/acl_ovn.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net" "strconv" "strings" @@ -1156,3 +1157,56 @@ func ovnParseLogEntry(input string, timestamp string, prefix string) string { return string(out) } + +// ovnParseLogEntriesFromJournald reads the OVN log entries from the systemd journal and returns them as a list of string entries. +func ovnParseLogEntriesFromJournald(ctx context.Context, systemdUnitName string, prefix string) ([]string, error) { + var logEntries []string + cmd := []string{ + "/usr/bin/journalctl", + "--unit", systemdUnitName, + "--directory", shared.HostPath("/var/log/journal"), + "--no-pager", + "--boot", "0", + "--case-sensitive", + "--grep", prefix, + "--output-fields", "MESSAGE", + "-n", "1000", + "-o", "json", + } + + stdout := strings.Builder{} + err := shared.RunCommandWithFds(ctx, nil, &stdout, cmd[0], cmd[1:]...) + if err != nil { + return nil, fmt.Errorf("Failed to run journalctl to fetch OVN ACL logs: %w", err) + } + + decoder := json.NewDecoder(strings.NewReader(stdout.String())) + for { + var sdLogEntry map[string]any + err = decoder.Decode(&sdLogEntry) + if err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("Failed to parse log entry: %w", err) + } + + message, ok := sdLogEntry["MESSAGE"].(string) + if !ok { + continue + } + + timestamp, ok := sdLogEntry["__REALTIME_TIMESTAMP"].(string) + if !ok { + continue + } + + logEntry := ovnParseLogEntry(message, timestamp, prefix) + if logEntry == "" { + continue + } + + logEntries = append(logEntries, logEntry) + } + + return logEntries, nil +} diff --git a/lxd/network/acl/driver_common.go b/lxd/network/acl/driver_common.go index 332da23aad13..b36f5d33d12e 100644 --- a/lxd/network/acl/driver_common.go +++ b/lxd/network/acl/driver_common.go @@ -751,33 +751,50 @@ func (d *common) Delete() error { // GetLog gets the ACL log. func (d *common) GetLog(clientType request.ClientType) (string, error) { // ACLs aren't specific to a particular network type but the log only works with OVN. - logPath := shared.HostPath("/var/log/ovn/ovn-controller.log") - if !shared.PathExists(logPath) { - return "", fmt.Errorf("Only OVN log entries may be retrieved at this time") - } + var logEntries []string + var err error + + // First, check if we can resolve the /run/openvswitch symlink to determine if OVN is in use. + // This is used in case of a MicroOVN deployment is interfaced with LXD. + targetPath, err := os.Readlink("/run/openvswitch") + if err == nil && strings.HasSuffix(targetPath, "/microovn/chassis/switch") { + prefix := fmt.Sprintf("lxd_acl%d-", d.id) + logEntries, err = ovnParseLogEntriesFromJournald(context.TODO(), "snap.microovn.chassis.service", prefix) + if err != nil { + return "", fmt.Errorf("Failed to get OVN log entries from syslog: %v\n", err) + } + } else { + // Else, if the /run/openvswitch symlink doesn't exist (which might be the case for a non-snap installation of LXD), + // then try to read the OVN controller log file directly. + logEntries = []string{} + prefix := fmt.Sprintf("lxd_acl%d-", d.id) + logPath := shared.HostPath("/var/log/ovn/ovn-controller.log") + if !shared.PathExists(logPath) { + return "", fmt.Errorf("Only OVN log entries may be retrieved at this time") + } - // Open the log file. - logFile, err := os.Open(logPath) - if err != nil { - return "", fmt.Errorf("Couldn't open OVN log file: %w", err) - } + // Open the log file. + logFile, err := os.Open(logPath) + if err != nil { + return "", fmt.Errorf("Failed to open OVN log file: %w", err) + } - defer func() { _ = logFile.Close() }() + defer func() { _ = logFile.Close() }() - logEntries := []string{} - scanner := bufio.NewScanner(logFile) - for scanner.Scan() { - logEntry := ovnParseLogEntry(scanner.Text(), "", fmt.Sprintf("lxd_acl%d-", d.id)) - if logEntry == "" { - continue - } + scanner := bufio.NewScanner(logFile) + for scanner.Scan() { + logEntry := ovnParseLogEntry(scanner.Text(), "", prefix) + if logEntry == "" { + continue + } - logEntries = append(logEntries, logEntry) - } + logEntries = append(logEntries, logEntry) + } - err = scanner.Err() - if err != nil { - return "", fmt.Errorf("Failed to read OVN log file: %w", err) + err = scanner.Err() + if err != nil { + return "", fmt.Errorf("Failed to read OVN log file: %w", err) + } } // Aggregates the entries from the rest of the cluster.