Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Release candidate after provision lifecycle hardening, runtime observability fix
### Changed

- **Live reprovision:** `edgelet system provision` on a running daemon reloads registries, volume mounts, microservices, and config without restarting background workers.
- **Split log basenames:** `edgelet.service` writes `edgelet.*.log`; `edgelet-containerd.service` writes `edgelet-containerd.*.log` under the same `logDiskDirectory` (60/40 rotation budget when runtime split is enabled). See `docs/edgelet/logging.md`.
- **Split log basenames:** `edgelet.service` writes `edgelet.*.log`; `edgelet-containerd.service` writes `edgelet-containerd.*.log` under the same `logDirectory` (60/40 rotation budget when runtime split is enabled). See `docs/edgelet/logging.md`.
- **Disk usage metric:** `edgelet system status` `diskUsage` walks the full configured `diskDirectory` instead of legacy `messages/archive` + `volumes/` partial sums; automatic archive prune removed (greenfield).
- **Image drift (embedded engine):** reconcile compares images with `imageref.Match` so controller short names (e.g. `user/repo:tag`) match runtime `docker.io/` prefixes without endless `config_drift` UPDATE loops.
- **Deprovision cleanup:** volume-mount index cleanup no longer holds locks across slow filesystem work that blocked the process-manager monitor during background deprovision.
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,12 @@ install-dev: build-edgelet-local ## Install edgelet and setup local dev environm
' networkInterface: "dynamic"' \
' containerEngine: "docker"' \
' containerEngineUrl: "unix:///var/run/docker.sock"' \
' diskConsumptionLimit: "10"' \
' diskLimit: "10"' \
" diskDirectory: \"$(DEV_VAR_LIB)/\"" \
' memoryConsumptionLimit: "4096"' \
' processorConsumptionLimit: "80.0"' \
' logDiskConsumptionLimit: "10.0"' \
" logDiskDirectory: \"$(DEV_VAR_LOG)/\"" \
' memoryLimit: "4096"' \
' cpuLimit: "80.0"' \
' logLimit: "10.0"' \
" logDirectory: \"$(DEV_VAR_LOG)/\"" \
' logFileCount: "10"' \
' logLevel: "DEBUG"' \
' statusFrequency: "30"' \
Expand Down
6 changes: 3 additions & 3 deletions cmd/edgelet-server/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func startLoggingService(basename ...string) {

cfg := config.GetInstance()
logo += fmt.Sprintf(" Log Level: %s\n", cfg.LogLevel)
logo += fmt.Sprintf(" Log Directory: %s\n", cfg.LogDiskDirectory)
logo += fmt.Sprintf(" Log Directory: %s\n", cfg.LogDirectory)
logo += fmt.Sprintf(" Log Basename: %s\n", logBasename)
_, _ = fmt.Print(logo)

Expand All @@ -209,8 +209,8 @@ func startLoggingService(basename ...string) {
role = logging.SeriesDataPlane
runtimeSplit = true
}
logDiskLimitMB := logging.DaemonLogBudgetMB(cfg.LogDiskLimit, role, runtimeSplit)
if err := logging.SetupLogger(cfg.LogDiskDirectory, logDiskLimitMB, cfg.LogFileCount, cfg.LogLevel, logBasename); err != nil {
logLimitMB := logging.DaemonLogBudgetMB(cfg.LogLimit, role, runtimeSplit)
if err := logging.SetupLogger(cfg.LogDirectory, logLimitMB, cfg.LogFileCount, cfg.LogLevel, logBasename); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to setup logger: %v\n", err)
exitDaemon(1)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/edgelet-server/runtime_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func reloadRuntimeBootstrapConfig() {
logging.LogError("RUNTIME_BOOTSTRAP", "Configuration validation failed after reload", err)
return
}
budgetMB := logging.DaemonLogBudgetMB(cfg.LogDiskLimit, logging.SeriesDataPlane, true)
if err := logging.InstanceConfigUpdated(cfg.LogDiskDirectory, budgetMB, cfg.LogFileCount, cfg.LogLevel, logging.BasenameDataPlane); err != nil {
budgetMB := logging.DaemonLogBudgetMB(cfg.LogLimit, logging.SeriesDataPlane, true)
if err := logging.InstanceConfigUpdated(cfg.LogDirectory, budgetMB, cfg.LogFileCount, cfg.LogLevel, logging.BasenameDataPlane); err != nil {
logging.LogError("RUNTIME_BOOTSTRAP", "Failed to update logger configuration", err)
return
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/edgelet/daemon_desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ func startLoggingService() {

cfg := config.GetInstance()
logo += fmt.Sprintf(" Log Level: %s\n", cfg.LogLevel)
logo += fmt.Sprintf(" Log Directory: %s\n", cfg.LogDiskDirectory)
logo += fmt.Sprintf(" Log Directory: %s\n", cfg.LogDirectory)
_, _ = fmt.Print(logo)

logDiskLimitMB := logging.DaemonLogBudgetMB(cfg.LogDiskLimit, logging.SeriesControlPlane, false)
if err := logging.SetupLogger(cfg.LogDiskDirectory, logDiskLimitMB, cfg.LogFileCount, cfg.LogLevel); err != nil {
logLimitMB := logging.DaemonLogBudgetMB(cfg.LogLimit, logging.SeriesControlPlane, false)
if err := logging.SetupLogger(cfg.LogDirectory, logLimitMB, cfg.LogFileCount, cfg.LogLevel); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to setup logger: %v\n", err)
exitDaemon(1)
}
Expand Down
4 changes: 2 additions & 2 deletions docs/cli/generated/edgelet_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ edgelet config [flags]
--gps-mode string GPS mode (auto|dynamic|manual|off). Alias: --gps
--gps-scan-frequency int GPS scan frequency. Alias: --gpsf
-h, --help help for config
--log-disk-directory string log directory. Alias: --ld
--log-disk-limit-gib float log disk limit (GiB). Alias: --l
--log-directory string log directory. Alias: --ld
--log-limit-gib float log limit (GiB). Alias: --l
--log-file-count int log file count. Alias: --lc
--log-level string log level (DEBUG|INFO|WARN|ERROR). Alias: --ll
--memory-limit-mib float memory limit (MiB). Alias: --m
Expand Down
2 changes: 1 addition & 1 deletion docs/edgelet/cgroups.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Per-microservice fields enforced on the edgelet engine (CRI/crun):
- `memoryLimit` (bytes)
- `cpuSetCpus`

Node configuration `memoryConsumptionLimit` / `processorConsumptionLimit` remain **monitor-only** (not cgroup-enforced).
Node configuration `memoryLimit` / `cpuLimit` remain **monitor-only** (not cgroup-enforced).

Missing **hugetlb** / **rdma** controllers on edge hardware are tolerated.

Expand Down
2 changes: 1 addition & 1 deletion docs/edgelet/init-systems.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ plane uses `supervise-daemon` respawn (`respawn_max=0`, parity with systemd `Res
| Log | Source |
|-----|--------|
| `/var/log/edgelet/daemon.log` | OpenRC `output_log` / `error_log` — thin wrapper and pre-exec errors |
| `/var/log/edgelet/edgelet.0.log` (rotated) | Fat daemon after `edgelet daemon` starts (`logDiskDirectory`) |
| `/var/log/edgelet/edgelet.0.log` (rotated) | Fat daemon after `edgelet daemon` starts (`logDirectory`) |

---

Expand Down
6 changes: 3 additions & 3 deletions docs/edgelet/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Set via config or environment (`LOG_LEVEL=info`).

## Log files (rotated)

When `logDiskDirectory` is configured (default `/var/log/edgelet/`), control plane and data plane write **separate file series** in the same directory:
When `logDirectory` is configured (default `/var/log/edgelet/`), control plane and data plane write **separate file series** in the same directory:

| systemd unit | Basename | Active file |
|--------------|----------|-------------|
Expand All @@ -78,9 +78,9 @@ Each series rotates independently using `logFileCount` and a share of **`logLimi
| Runtime split (`EDGELET_RUNTIME_SPLIT=1`, embedded engine) | 60% | 40% |
| Monolithic embedded, docker, podman, desktop | 100% | (unit not used) |

**Hot reload:** `logLevel`, `logLimit`, and `logFileCount` apply on config reload without rotating the active log file. Control plane reloads via SIGHUP / `edgelet system reload`; data plane reloads via `systemctl kill -s HUP edgelet-containerd`. Changing `logDiskDirectory` still requires a process restart.
**Hot reload:** `logLevel`, `logLimit`, and `logFileCount` apply on config reload without rotating the active log file. Control plane reloads via SIGHUP / `edgelet system reload`; data plane reloads via `systemctl kill -s HUP edgelet-containerd`. Changing `logDirectory` still requires a process restart.

Microservice logs under `logDiskDirectory/containers/` use separate per-UUID series (not included in the daemon 60/40 split).
Microservice logs under `logDirectory/containers/` use separate per-UUID series (not included in the daemon 60/40 split).

## journald

Expand Down
2 changes: 1 addition & 1 deletion docs/edgelet/modules/engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Supervisor retries docker/podman init; if socket unavailable:
| `containerEngine` | Factory selector |
| `containerEngineUrl` | Socket/endpoint for external engines |
| `dockerApiVersion` | Docker API version string |
| `logDiskDirectory` | Container log root passed to edgelet engine |
| `logDirectory` | Container log root passed to edgelet engine |

## Interface surface

Expand Down
4 changes: 2 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2656,7 +2656,7 @@ profiles:
containerEngine: ${_eng}
containerEngineUrl: ${_du}
diskDirectory: ${_disk}
logDiskDirectory: ${_log_disk}
logDirectory: ${_log_disk}
logLevel: INFO
YAML
chmod 640 "$CONFIG_FILE"
Expand Down Expand Up @@ -3291,7 +3291,7 @@ profiles:
containerEngine: ${_eng}
containerEngineUrl: ${_du}
diskDirectory: ${_disk}
logDiskDirectory: ${_log_disk}
logDirectory: ${_log_disk}
logLevel: INFO
YAML
chmod 640 "$CONFIG_FILE"
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/domain/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ var longFlagNames = map[string]string{
"diskDirectory": "disk-directory",
"memoryLimitMiB": "memory-limit-mib",
"cpuLimitPercent": "cpu-limit-percent",
"logDiskLimitGiB": "log-disk-limit-gib",
"logDiskDirectory": "log-disk-directory",
"logLimitGiB": "log-limit-gib",
"logDirectory": "log-directory",
"logFileCount": "log-file-count",
"logLevel": "log-level",
"statusFrequencySeconds": "status-frequency-seconds",
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/domain/config/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ func TestLongFlagName_ExplicitGiBAndMiB(t *testing.T) {
if got := longFlagName("memoryLimitMiB"); got != "memory-limit-mib" {
t.Fatalf("memoryLimitMiB=%q", got)
}
if got := longFlagName("logDiskLimitGiB"); got != "log-disk-limit-gib" {
t.Fatalf("logDiskLimitGiB=%q", got)
if got := longFlagName("logLimitGiB"); got != "log-limit-gib" {
t.Fatalf("logLimitGiB=%q", got)
}
}

Expand Down
4 changes: 2 additions & 2 deletions internal/cli/domain/config/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ var configKeyRules = map[string]configKeyRule{
"diskDirectory": {Key: "diskDirectory", Aliases: []string{"dl"}, Type: configValueString, Help: "disk directory"},
"memoryLimitMiB": {Key: "memoryLimitMiB", Aliases: []string{"m"}, Type: configValueFloat, Help: "memory limit (MiB)"},
"cpuLimitPercent": {Key: "cpuLimitPercent", Aliases: []string{"p"}, Type: configValueFloat, Help: "CPU limit (%)"},
"logDiskLimitGiB": {Key: "logDiskLimitGiB", Aliases: []string{"l"}, Type: configValueFloat, Help: "log disk limit (GiB)"},
"logDiskDirectory": {Key: "logDiskDirectory", Aliases: []string{"ld"}, Type: configValueString, Help: "log directory"},
"logLimitGiB": {Key: "logLimitGiB", Aliases: []string{"l"}, Type: configValueFloat, Help: "log limit (GiB)"},
"logDirectory": {Key: "logDirectory", Aliases: []string{"ld"}, Type: configValueString, Help: "log directory"},
"logFileCount": {Key: "logFileCount", Aliases: []string{"lc"}, Type: configValueInt, Help: "log file count"},
"logLevel": {Key: "logLevel", Aliases: []string{"ll"}, Type: configValueString, Enums: []string{"DEBUG", "INFO", "WARN", "ERROR"}, Help: "log level"},
"statusFrequencySeconds": {Key: "statusFrequencySeconds", Aliases: []string{"sf"}, Type: configValueInt, Help: "status frequency (seconds)"},
Expand Down
3 changes: 1 addition & 2 deletions internal/cli/output/edgeletapi_human.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var infoOutputOrder = []string{
"diskLimit",
"diskDirectory",
"logLimit",
"logFileDirectory",
"logDirectory",
"logFilesCount",
"logLevel",
"upgradeScanFrequency",
Expand All @@ -73,7 +73,6 @@ var infoAliasToCanonical = map[string]string{
"cpuLimit": "cpuUsageLimit",
"memoryLimit": "memoryRamLimit",
"diskLimit": "diskUsageLimit",
"logLimit": "logDiskLimit",
"logLevel": "logFilesLevel",
"upgradeScanFrequency": "readyToUpgradeScanFrequency",
"deviceScanFrequency": "scanDevicesFrequency",
Expand Down
10 changes: 5 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type Config struct {
MemoryLimit float64
DiskDirectory string
CPULimit float64
LogDiskLimit float64
LogDiskDirectory string
LogLimit float64
LogDirectory string
LogFileCount int
LogLevel string
StatusFrequency int
Expand Down Expand Up @@ -362,11 +362,11 @@ func (c *Config) GetConfigReportWithIP(ipAddress string) string {
// CPU limit
buildLine("CPU Usage Limit", fmt.Sprintf("%.2f%%", c.CPULimit))

// Log disk limit
buildLine("Log Disk Limit", fmt.Sprintf("%.2f GiB", c.LogDiskLimit))
// Log limit
buildLine("Log Limit", fmt.Sprintf("%.2f GiB", c.LogLimit))

// Log directory
buildLine("Log File Directory", c.LogDiskDirectory)
buildLine("Log Directory", c.LogDirectory)

// Log file count
buildLine("Log Files Count", fmt.Sprintf("%d", c.LogFileCount))
Expand Down
148 changes: 148 additions & 0 deletions internal/config/config_diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package config

import (
"fmt"
"strconv"
"strings"

"github.com/eclipse-iofog/edgelet/internal/constants"
)

// FilterChangedConfigKeys returns only entries whose normalized value differs from
// the current in-memory config. Used when applying full controller config snapshots
// so unchanged keys are not re-written or validated as mutations.
func (c *Config) FilterChangedConfigKeys(configMap map[string]any) map[string]any {
if len(configMap) == 0 {
return nil
}

c.mu.RLock()
defer c.mu.RUnlock()

effectiveEngine := c.effectiveEngineForBatchLocked(configMap)
changed := make(map[string]any, len(configMap))
for option, value := range configMap {
if _, ok := ConfigParamMap[option]; !ok {
continue
}
if c.configKeyUnchangedLocked(option, value, effectiveEngine) {
continue
}
changed[option] = value
}
return changed
}

func (c *Config) effectiveEngineForBatchLocked(configMap map[string]any) string {
if raw, ok := configMap["ce"]; ok {
return normalizeIncomingValue(raw)
}
return c.ContainerEngine
}

func (c *Config) configKeyUnchangedLocked(option string, value any, effectiveEngine string) bool {
incoming := normalizeIncomingValue(value)
fieldName := ConfigParamMap[option]

switch fieldName {
case "diskLimit":
v, err := strconv.ParseFloat(incoming, 64)
return err == nil && v == c.DiskLimit
case "diskDirectory":
return incoming == c.DiskDirectory
case "memoryLimit":
v, err := strconv.ParseFloat(incoming, 64)
return err == nil && v == c.MemoryLimit
case "cpuLimit":
v, err := strconv.ParseFloat(incoming, 64)
return err == nil && v == c.CPULimit
case "controllerURL":
return incoming == c.ControllerURL
case "controllerCert":
return incoming == c.ControllerCert
case "containerEngineURL":
if strings.EqualFold(strings.TrimSpace(effectiveEngine), constants.EngineEdgelet) {
return strings.TrimSpace(incoming) == constants.EdgeletEngineSocketURL()
}
return strings.TrimSpace(incoming) == strings.TrimSpace(c.ContainerEngineURL)
case "containerEngine":
return strings.EqualFold(incoming, c.ContainerEngine)
case "networkInterface":
return incoming == c.NetworkInterface
case "logLimit":
v, err := strconv.ParseFloat(incoming, 64)
return err == nil && v == c.LogLimit
case "logDirectory":
return incoming == c.LogDirectory
case "logFileCount":
v, err := strconv.Atoi(incoming)
return err == nil && v == c.LogFileCount
case "logLevel":
return strings.EqualFold(incoming, c.LogLevel)
case "statusFrequency":
v, err := strconv.Atoi(incoming)
return err == nil && v == c.StatusFrequency
case "changeFrequency":
v, err := strconv.Atoi(incoming)
return err == nil && v == c.ChangeFrequency
case "deviceScanFrequency":
v, err := strconv.Atoi(incoming)
return err == nil && v == c.DeviceScanFrequency
case "watchdogEnabled":
return watchdogEnabledEqual(incoming, c.WatchdogEnabled)
case "edgeGuardFrequency":
v, err := strconv.ParseInt(incoming, 10, 64)
return err == nil && v == c.EdgeGuardFrequency
case "gpsMode":
return strings.EqualFold(incoming, c.GPSMode)
case "gpsCoordinates":
current, err := normalizeGPSCoordinates(c.GPSCoordinates)
if err != nil {
return false
}
incomingNorm, err := normalizeGPSCoordinates(incoming)
return err == nil && incomingNorm == current
case "gpsDevice":
return incoming == c.GPSDevice
case "gpsScanFrequency":
v, err := strconv.ParseInt(incoming, 10, 64)
return err == nil && v == c.GPSScanFrequency
case "arch":
return strings.EqualFold(incoming, c.Arch)
case "secureMode":
return ParseSecureMode(incoming) == c.SecureMode
case "pruningFrequency":
v, err := strconv.ParseInt(incoming, 10, 64)
return err == nil && v == c.PruningFrequency
case "availableDiskThreshold":
v, err := strconv.ParseInt(incoming, 10, 64)
return err == nil && v == c.AvailableDiskThreshold
case "upgradeScanFrequency":
v, err := strconv.Atoi(incoming)
return err == nil && v == c.UpgradeScanFrequency
case "devMode":
return devModeEqual(incoming, c.DevMode)
case "timeZone":
return incoming == c.TimeZone
default:
return false
}
}

func normalizeIncomingValue(value any) string {
return strings.TrimPrefix(strings.TrimSpace(fmt.Sprintf("%v", value)), "+")
}

func watchdogEnabledEqual(incoming string, current bool) bool {
normalized := strings.ToLower(strings.TrimSpace(incoming))
switch normalized {
case "off", "false", "0", "no":
return !current
default:
return current
}
}

func devModeEqual(incoming string, current bool) bool {
return (strings.ToLower(strings.TrimSpace(incoming)) != "off") == current
}
Loading
Loading