Skip to content

fix: bounded concurrency and thread-safe digest for slow devices - #752

Open
amarnath-ac wants to merge 1 commit into
mainfrom
1082_device_details_en_off_fix
Open

fix: bounded concurrency and thread-safe digest for slow devices#752
amarnath-ac wants to merge 1 commit into
mainfrom
1082_device_details_en_off_fix

Conversation

@amarnath-ac

@amarnath-ac amarnath-ac commented Jul 23, 2026

Copy link
Copy Markdown

Issue 1082: Device Details Page Does Not Load When Device Is Powered Off or Sleeping.

Problem statement:
When an AMT device is in Soft Off or Sleep state (no CIRA), the Device Details page fails to load, resulting in an infinite spinner / 504 errors.

Root cause:
When the device is powered off or sleeping, AMT firmware can only service a couple of WSMAN requests at a time. The Device Details page fires ~11 at once, so most of them queue inside the firmware until they time out or their TLS connections get dropped. Powered on, the firmware handles the same burst fine, which is why the page only fails in the off state. Parallel requests also shared one digest auth object without locking, corrupting each other's auth state.

Fix:
protect shared digest auth with a mutex, reuse connections so the slow handshake happens only once, and limit concurrent requests per device.

Changes added:

  1. Cap concurrent Posts per device at 2.
  2. Single-flight the first authentication so only one Post wakes a sleeping device; concurrent siblings wait, then flow once primed.
  3. Serialize digest challenge access under a mutex so concurrent Posts get monotonic nonce-counts, reset the counter when AMT issues a fresh nonce to avoid auth lockout.
  4. Retry transient transport failures, rebuilding the digest header on each attempt.

@amarnath-ac
amarnath-ac force-pushed the 1082_device_details_en_off_fix branch 3 times, most recently from a856b6e to e55874d Compare July 23, 2026 14:23
@amarnath-ac

Copy link
Copy Markdown
Author

Console working logs:
ac@intel-H370M-D3H: go run -ldflags "-X github.com/device-management-toolkit/console/internal/app.Version=v1.35.0" ./cmd/app -config ./config/config.yml
2026/07/24 10:36:16 migrate: environment variable not declared: DB_URL -- using embedded database
2026/07/24 10:36:16 DB path : /home/ac/.config/device-management-toolkit/console.db
2026/07/24 10:36:16 Encryption key loaded from environment
{"level":"info","time":"2026-07-24T10:36:16+05:30","caller":"/home/ac/dmt/code/console_latest/console/cmd/app/main.go:79","message":"UI assets not embedded; skipping browser launch"}
{"level":"info","time":"2026-07-24T10:36:16+05:30","caller":"/home/ac/dmt/code/console_latest/console/cmd/app/main.go:35","message":"app - Run - version: v1.35.0"}
{"level":"warn","time":"2026-07-24T10:36:16+05:30","caller":"/home/ac/dmt/code/console_latest/console/internal/controller/httpapi/ui.go:47","message":"Could not read embedded main.js: open ui/main.js: file does not exist"}

{"level":"info","time":"2026-07-24T10:36:24+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:24 | 200 | 923.98ms | 127.0.0.1 | GET "/api/v1/amt/power/state/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}

{"level":"info","time":"2026-07-24T10:36:39+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:39 | 200 | 93.79µs | 127.0.0.1 | GET "/api/v1/server/features""}
{"level":"info","time":"2026-07-24T10:36:39+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:39 | 200 | 688.316µs | 127.0.0.1 | GET "/api/v1/devices/tags""}
{"level":"info","time":"2026-07-24T10:36:39+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:39 | 200 | 350.602µs | 127.0.0.1 | GET "/api/v1/devices?$top=25&$skip=0&$count=true""}
{"level":"info","time":"2026-07-24T10:36:39+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:39 | 200 | 424.28ms | 127.0.0.1 | GET "/version""}

{"level":"info","time":"2026-07-24T10:36:46+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:46 | 200 | 632.229µs | 127.0.0.1 | GET "/api/v1/devices/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:36:46+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:46 | 200 | 278.46ms | 127.0.0.1 | GET "/api/v1/amt/features/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:36:47+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:47 | 200 | 675.8ms | 127.0.0.1 | GET "/api/v1/amt/version/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:36:47+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:47 | 200 | 1.04s | 127.0.0.1 | GET "/api/v1/amt/generalSettings/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:36:48+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:36:48 | 200 | 1.32s | 127.0.0.1 | GET "/api/v1/amt/power/state/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}

{"level":"info","time":"2026-07-24T10:37:03+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:03 | 200 | 233.62ms | 127.0.0.1 | POST "/api/v1/amt/power/action/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}

{"level":"info","time":"2026-07-24T10:37:18+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:18 | 200 | 794.89ms | 127.0.0.1 | GET "/api/v1/amt/power/state/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}

{"level":"info","time":"2026-07-24T10:37:30+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:30 | 200 | 48.666µs | 127.0.0.1 | GET "/api/v1/server/features""}
{"level":"info","time":"2026-07-24T10:37:30+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:30 | 200 | 606.144µs | 127.0.0.1 | GET "/api/v1/devices/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:37:31+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:31 | 200 | 451.34ms | 127.0.0.1 | GET "/version""}
{"level":"info","time":"2026-07-24T10:37:32+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:32 | 200 | 2.01s | 127.0.0.1 | GET "/api/v1/amt/generalSettings/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:37:38+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:38 | 200 | 7.46s | 127.0.0.1 | GET "/api/v1/amt/version/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:37:47+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:47 | 200 | 16.28s | 127.0.0.1 | GET "/api/v1/amt/power/state/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}
{"level":"info","time":"2026-07-24T10:37:48+05:30","caller":"/home/ac/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.linux-amd64/src/fmt/print.go:263","message":"[GIN] 2026/07/24 - 10:37:48 | 200 | 18.01s | 127.0.0.1 | GET "/api/v1/amt/features/e51c2a64-4cb7-4fb1-b55b-2ee4af0597c9""}

@amarnath-ac
amarnath-ac force-pushed the 1082_device_details_en_off_fix branch from 28d4d0e to 4f11c90 Compare July 28, 2026 14:41
@amarnath-ac amarnath-ac changed the title fix: digest challenge synchronization fix: bounded concurrency and thread-safe digest for slow devices Jul 28, 2026
@amarnath-ac
amarnath-ac force-pushed the 1082_device_details_en_off_fix branch 3 times, most recently from b875897 to 51c4dbe Compare July 28, 2026 15:56
@amarnath-ac
amarnath-ac marked this pull request as ready for review July 28, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses console timeouts and AMT auth lockouts on slow/offline devices by making the WSMAN HTTP client more resilient under parallel load (bounded concurrency, connection reuse, and thread-safe Digest state).

Changes:

  • Introduces per-target bounded concurrency for Post and aligns HTTP transport limits to reduce ME connection flooding.
  • Adds mutex-protected Digest challenge handling plus a “prime” single-flight to avoid parallel cold-wake handshakes.
  • Adds transient transport retry logic and enables keep-alives to avoid repeated slow TLS handshakes.

Comment thread pkg/wsman/client/wsman.go Outdated
Comment thread pkg/wsman/client/wsman.go
Comment thread pkg/wsman/client/wsman.go Outdated
@amarnath-ac
amarnath-ac force-pushed the 1082_device_details_en_off_fix branch 3 times, most recently from d0c1ee1 to d839cd7 Compare July 30, 2026 06:01
1. Cap concurrent Posts per device at 7 (matches AMT ME capacity).
2. Single-flight the first authentication so only one Post wakes a
   sleeping device; concurrent siblings wait, then flow once primed.
3. Serialize digest challenge access under a mutex so concurrent
   Posts get monotonic nonce-counts, reset the counter when AMT
   issues a fresh nonce to avoid auth lockout.
4. Retry transient transport failures, rebuilding the digest header
   on each attempt.

Signed-off-by: C, Amarnath <amarnath.c@intel.com>
@sudhir-intc

Copy link
Copy Markdown
Contributor

@amarnath-ac : Can you add evidence ( in the form of logs ) to showcase the failures that you have mentioned occuring and post the fix how this is getting handled.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

pkg/wsman/client/wsman.go:86

  • hostSems/hostSemsMu introduce package-level mutable global state to share semaphores across Targets. This makes concurrency limiting implicit and unbounded (every unique endpoint string is retained forever), which can grow memory over time in long-running processes and violates the repo guideline to avoid package-level mutable state.

Consider making the limiter explicit and lifecycle-managed (e.g., add an optional semaphore/limiter field to client.Parameters or a small Limiter interface, so callers that create multiple clients for the same device can share it without a global map; default to a per-Target limiter when not provided).

// hostSems limits concurrency per device, shared across all Targets for a host.
var (
	hostSemsMu sync.Mutex
	hostSems   = make(map[string]chan struct{})
)

pkg/wsman/client/wsman.go:136

  • The deadline budget in retryOnTransient is only checked before each retry attempt. Since each t.Do(...) call can still block up to t.Timeout (30s) independently, the total time holding the per-host semaphore can exceed the intended timeout budget (and potentially approach timeout * (retries+1)).

To enforce a true overall budget, create a request context with the shared deadline/timeout in Post, apply it to the initial request, and reuse the same context for all retry requests (e.g., pass a context.Context into retryOnTransient and call req.WithContext(ctx) on each attempt).

	for attempt := 1; attempt <= maxTransientRetries; attempt++ {
		// All attempts share one budget so a Post cannot hold its slot for
		// maxTransientRetries * timeout.
		if time.Now().After(deadline) {
			return nil, lastErr
		}

@sudhir-intc sudhir-intc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM with some comments to be updated.

Comment thread pkg/wsman/client/wsman.go
@@ -153,9 +254,9 @@ func NewWsman(cp Parameters) *Target {
}

res.Transport = &http.Transport{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment here for the DisableKeepAlives: false so that in future it may not be turned to try.

Comment thread pkg/wsman/client/wsman.go
}

const timeout = 10 * time.Second
// hostSems limits concurrency per device, shared across all Targets for a host.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// hostSems limits concurrency per device, shared across all Targets for a host.
// hostSems stores process-wide semaphores keyed by endpoint host:port.
// Each semaphore caps concurrent WSMAN Posts to that endpoint.

Comment thread pkg/wsman/client/wsman.go
hostSems = make(map[string]chan struct{})
)

// hostSemaphore returns the shared concurrency limiter for an endpoint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// hostSemaphore returns the shared concurrency limiter for an endpoint.
// hostSemaphore returns the shared per-endpoint concurrency limiter.
// The same host:port gets the same semaphore across all Target instances.

Comment thread pkg/wsman/client/wsman.go
WSManPath = "/wsman"
timeout = 30 * time.Second
idleConnTimeout = 90 * time.Second // keeps sockets warm as long as AMT holds them open
maxConcurrentPosts = 2 // caps simultaneous WSMAN Posts to match AMT firmware capacity

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
maxConcurrentPosts = 2 // caps simultaneous WSMAN Posts to match AMT firmware capacity
maxConcurrentPosts = 2 // Keep at 2 to avoid overload during powered-off wake paths; higher concurrency caused transient auth/transport failures.

Comment thread pkg/wsman/client/wsman.go
lastErr := origErr

for attempt := 1; attempt <= maxTransientRetries; attempt++ {
// All attempts share one budget so a Post cannot hold its slot for

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// All attempts share one budget so a Post cannot hold its slot for
// Soft overall budget guard: stop scheduling new retry attempts once the shared
// deadline has passed. In-flight t.Do calls can still run until client/request
// timeout, so this limits additional retries rather than hard-capping total wall time.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct the comment here.

@sudhir-intc

sudhir-intc commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tested these changes along with the console PR#device-management-toolkit/console#1153 and here's my observations

  • Functionality in powered off state has improved. Don't see the context timeout issues
  • Requests made towards the device across various things like General AMT info, Hardware information and switching across them, I see better user experience w.r.t the information getting loaded. There a good improvement in the REST API's request and response time. Please see the logs below:

With the fixes:

{"level":"info","time":"2026-07-31T15:46:05+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:05 | 200 |    1.92s |  10.247.148.120 | GET      \"/api/v1/amt/generalSettings/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:06+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:06 | 200 |    3.46s |  10.247.148.120 | GET      \"/api/v1/amt/version/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:13+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:13 | 200 |    2.23s |  10.247.148.120 | GET      \"/api/v1/amt/hardwareInfo/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:14+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:14 | 200 |    1.48s |  10.247.148.120 | GET      \"/api/v1/amt/diskInfo/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:21+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:21 | 200 | 417.29ms |  10.247.148.120 | GET      \"/api/v1/amt/generalSettings/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:22+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:22 | 200 |    1.54s |  10.247.148.120 | GET      \"/api/v1/amt/version/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:27+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:27 | 200 | 928.28ms |  10.247.148.120 | POST     \"/api/v1/amt/power/action/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:40+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:40 | 200 |    7.94s |  10.247.148.120 | GET      \"/api/v1/amt/power/state/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}
{"level":"info","time":"2026-07-31T15:46:40+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:46:40 | 200 |  97.93ms |  10.247.148.120 | GET      \"/api/v1/amt/power/state/b8d2c9cf-4e2f-0ba4-6f16-88aedd6e42b2\""}

Without fixes

{"level":"info","time":"2026-07-31T15:50:30+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:50:30 | 200 |    1.12s |  10.247.148.120 | GET      \"/api/v1/amt/generalSettings/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"info","time":"2026-07-31T15:50:32+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:50:32 | 200 |    2.38s |  10.247.148.120 | GET      \"/api/v1/amt/version/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"info","time":"2026-07-31T15:50:37+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:50:37 | 200 | 502.37ms |  10.247.148.120 | POST     \"/api/v1/amt/power/action/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"info","time":"2026-07-31T15:50:44+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:50:44 | 200 |    1.86s |  10.247.148.120 | GET      \"/api/v1/amt/power/state/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"warn","time":"2026-07-31T15:57:07+05:30","caller":"log/log.go:244","message":"http: TLS handshake error from 10.247.148.120:5053: remote error: tls: unknown certificate"}
{"level":"warn","time":"2026-07-31T15:57:07+05:30","caller":"log/log.go:244","message":"http: TLS handshake error from 10.247.148.120:22707: remote error: tls: unknown certificate"}
{"level":"warn","time":"2026-07-31T15:57:07+05:30","caller":"log/log.go:244","message":"http: TLS handshake error from 10.247.148.120:61194: remote error: tls: unknown certificate"}
{"level":"info","time":"2026-07-31T15:57:07+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:57:07 | 200 |   1.65ms |  10.247.148.120 | GET      \"/api/v1/devices/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"info","time":"2026-07-31T15:57:10+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:57:10 | 200 |    2.99s |  10.247.148.120 | GET      \"/api/v1/amt/generalSettings/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"info","time":"2026-07-31T15:57:11+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:57:11 | 200 |    3.91s |  10.247.148.120 | GET      \"/api/v1/amt/version/514c4441-3134-3058-b031-4a4741444c51\""}
{"level":"warn","time":"2026-07-31T15:57:34+05:30","caller":"log/log.go:244","message":"http: TLS handshake error from 10.247.148.120:25843: remote error: tls: unknown certificate"}
**{"level":"error","time":"2026-07-31T15:57:44+05:30","caller":"github.com/gin-gonic/gin@v1.12.0/context.go:192","message":"Post \"https://10.49.97.147:16993/wsman\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)%!(EXTRA string=http - v1 - getPowerState)"}
{"level":"info","time":"2026-07-31T15:57:44+05:30","caller":"fmt/print.go:263","message":"[GIN] 2026/07/31 - 15:57:44 | 504 |      10s |  10.247.148.120 | GET      \"/api/v1/amt/power/state/514c4441-3134-3058-b031-4a4741444c51\""}**

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants