Skip to content

Commit

Permalink
lxd/network/acl: Read OVN logs from systemd journal
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Gabriel Mougard <[email protected]>
  • Loading branch information
gabrielmougard committed Dec 2, 2024
1 parent cf64979 commit 28a1c70
Showing 1 changed file with 77 additions and 5 deletions.
82 changes: 77 additions & 5 deletions lxd/network/acl/acl_ovn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -1042,7 +1044,8 @@ type ovnLogEntry struct {
}

// ovnParseLogEntry takes a log line and expected ACL prefix and returns a re-formated log entry if matching.
func ovnParseLogEntry(input string, prefix string) string {
// The 'timestamp' string is in microseconds format. If empty, the timestamp is extracted from the log entry.
func ovnParseLogEntry(input string, timestamp string, prefix string) string {
fields := strings.Split(input, "|")

// Skip unknown formatting.
Expand Down Expand Up @@ -1071,10 +1074,26 @@ func ovnParseLogEntry(input string, prefix string) string {
return ""
}

// Parse the timestamp.
logTime, err := time.Parse(time.RFC3339, fields[0])
if err != nil {
return ""
var logTime time.Time
var err error
// When reading from a log file, the timestamp is provided as a message parameter.
if timestamp == "" {
// Parse the timestamp.
logTime, err = time.Parse(time.RFC3339, fields[0])
if err != nil {
return ""
}
} else {
// Else, if from syslog, the timestamp is provided as a separate field
// which is passed separately.
tsInt, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return ""
}

// The provided timestamp is in microseconds and need to be converted to nanoseconds.
tsNs := tsInt * 1000
logTime = time.Unix(0, tsNs).UTC()
}

// Get the protocol.
Expand Down Expand Up @@ -1131,3 +1150,56 @@ func ovnParseLogEntry(input 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(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(context.TODO(), 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
}

0 comments on commit 28a1c70

Please sign in to comment.