Skip to content

Commit 5439c2f

Browse files
committed
Remove UUID handling and fix unit test
1 parent 071b288 commit 5439c2f

File tree

7 files changed

+123
-160
lines changed

7 files changed

+123
-160
lines changed

agent/client.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
type Client struct {
1313
serverURL string
1414
token string
15-
uuid string
1615
httpClient *http.Client
1716
}
1817

@@ -26,10 +25,6 @@ func NewClient(serverURL, token string) *Client {
2625
}
2726
}
2827

29-
func (c *Client) SetUUID(uuid string) {
30-
c.uuid = uuid
31-
}
32-
3328
func (c *Client) doRequest(method, path string, body interface{}) (*http.Response, error) {
3429
var bodyReader io.Reader
3530
if body != nil {
@@ -45,9 +40,6 @@ func (c *Client) doRequest(method, path string, body interface{}) (*http.Respons
4540
return nil, err
4641
}
4742
req.Header.Set("Authorization", "Bearer "+c.token)
48-
if c.uuid != "" {
49-
req.Header.Set("X-Agent-UUID", c.uuid)
50-
}
5143
if body != nil {
5244
req.Header.Set("Content-Type", "application/json")
5345
}

agent/flock_unix.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

agent/flock_windows.go

Lines changed: 0 additions & 45 deletions
This file was deleted.

agent/models.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ type StatusReport struct {
7474

7575
// HeartbeatRequest is sent periodically with agent metrics.
7676
type HeartbeatRequest struct {
77-
UUID string `json:"uuid,omitempty"`
7877
CPUPercent float64 `json:"cpu_percent"`
7978
MemPercent float64 `json:"mem_percent"`
8079
MemUsedBytes int64 `json:"mem_used_bytes"`

agent/worker.go

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package agent
33
import (
44
"bufio"
55
"context"
6-
"crypto/rand"
76
"fmt"
87
"os"
98
"os/exec"
@@ -25,106 +24,27 @@ type Worker struct {
2524
docker DockerConfig
2625
vcsOpts vcs.Options
2726
pollInterval time.Duration
28-
uuid string
29-
slotCleanup func()
3027
log *logrus.Entry
3128

3229
metricsMu sync.Mutex
3330
lastCounters *RawCounters
3431
}
3532

3633
func NewWorker(client *Client, docker DockerConfig, vcsOpts vcs.Options) *Worker {
37-
uuid, cleanup := acquireAgentSlot()
38-
client.SetUUID(uuid)
3934
return &Worker{
4035
client: client,
4136
docker: docker,
4237
vcsOpts: vcsOpts,
4338
pollInterval: 1 * time.Second,
44-
uuid: uuid,
45-
slotCleanup: cleanup,
4639
log: logrus.WithField("component", "agent"),
4740
}
4841
}
4942

50-
// agentSlotDir returns the directory for agent UUID slot files.
51-
func agentSlotDir() string {
52-
dir, err := os.UserConfigDir()
53-
if err != nil {
54-
dir = os.TempDir()
55-
}
56-
return filepath.Join(dir, "actionforge")
57-
}
58-
59-
// acquireAgentSlot finds and locks the lowest available agent slot.
60-
// Each slot has a persistent UUID file and a lock file. When the process
61-
// exits, the lock is released so the next process can reuse that slot
62-
// (and its UUID/metrics history).
63-
// Returns the UUID and a cleanup function that releases the lock.
64-
func acquireAgentSlot() (string, func()) {
65-
dir := agentSlotDir()
66-
_ = os.MkdirAll(dir, 0700)
67-
68-
const maxSlots = 256
69-
for i := 0; i < maxSlots; i++ {
70-
lockPath := filepath.Join(dir, fmt.Sprintf("agent-%d.lock", i))
71-
uuidPath := filepath.Join(dir, fmt.Sprintf("agent-%d.uuid", i))
72-
73-
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
74-
if err != nil {
75-
continue
76-
}
77-
78-
if err := lockFileExclusive(lockFile); err != nil {
79-
if cerr := lockFile.Close(); cerr != nil {
80-
logrus.WithError(cerr).Warn("failed to close lock file")
81-
}
82-
continue
83-
}
84-
85-
// Slot acquired — read or generate UUID
86-
uuid := ""
87-
if data, err := os.ReadFile(uuidPath); err == nil {
88-
if id := strings.TrimSpace(string(data)); len(id) == 36 {
89-
uuid = id
90-
}
91-
}
92-
if uuid == "" {
93-
var buf [16]byte
94-
_, _ = rand.Read(buf[:])
95-
buf[6] = (buf[6] & 0x0f) | 0x40 // version 4
96-
buf[8] = (buf[8] & 0x3f) | 0x80 // variant 1
97-
uuid = fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
98-
buf[0:4], buf[4:6], buf[6:8], buf[8:10], buf[10:16])
99-
_ = os.WriteFile(uuidPath, []byte(uuid+"\n"), 0600)
100-
}
101-
102-
cleanup := func() {
103-
unlockFile(lockFile)
104-
if cerr := lockFile.Close(); cerr != nil {
105-
logrus.WithError(cerr).Warn("failed to close lock file")
106-
}
107-
}
108-
return uuid, cleanup
109-
}
110-
111-
// Fallback: all slots taken, generate ephemeral UUID with no lock
112-
var buf [16]byte
113-
_, _ = rand.Read(buf[:])
114-
buf[6] = (buf[6] & 0x0f) | 0x40
115-
buf[8] = (buf[8] & 0x3f) | 0x80
116-
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
117-
buf[0:4], buf[4:6], buf[6:8], buf[8:10], buf[10:16]), func() {}
118-
}
119-
12043
// maxConsecutiveErrors is the number of consecutive connection errors before
12144
// Run returns ErrConnectionLost so the caller can decide to restart.
12245
const maxConsecutiveErrors = 10
12346

12447
func (w *Worker) Run(ctx context.Context) error {
125-
if w.slotCleanup != nil {
126-
defer w.slotCleanup()
127-
}
12848
w.log.Info("starting")
12949

13050
// Take initial snapshot for delta computation
@@ -632,13 +552,13 @@ func (w *Worker) buildHeartbeatRequest() HeartbeatRequest {
632552
snap, err := Snapshot()
633553
if err != nil {
634554
w.log.WithError(err).Warn("metrics snapshot error")
635-
return HeartbeatRequest{UUID: w.uuid}
555+
return HeartbeatRequest{}
636556
}
637557

638558
w.metricsMu.Lock()
639559
defer w.metricsMu.Unlock()
640560

641-
req := HeartbeatRequest{UUID: w.uuid}
561+
req := HeartbeatRequest{}
642562
if w.lastCounters != nil {
643563
// CPU percent
644564
if snap.CPUInstant {
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1-
//depot/pong/background.jpg#1 - add change 3 (binary+F)
2-
//depot/pong/build-pong-game.act#1 - add change 3 (text)
3-
//depot/pong/tutorial-readme.md#1 - add change 3 (text)
1+
build hasn't expired yet
2+
looking for value: 'env_file'
3+
no value (is optional) found for: 'env_file'
4+
looking for value: 'config_file'
5+
no value (is optional) found for: 'config_file'
6+
looking for value: 'concurrency'
7+
no value (is optional) found for: 'concurrency'
8+
looking for value: 'graph_file'
9+
no value (is optional) found for: 'graph_file'
10+
looking for value: 'session_token'
11+
no value (is optional) found for: 'session_token'
12+
looking for value: 'create_debug_session'
13+
found value in flags
14+
evaluated to: 'false'
15+
PushNodeVisit: start, execute: true
16+
🟢 Execute 'P4 Run (list-files)'
17+
PushNodeVisit: list-files, execute: true
18+
🟢 Execute 'Print (print-result)'
19+
PushNodeVisit: print-result, execute: true
20+
PushNodeVisit: (cached) list-files, execute: false
21+
[REDACTED]/background.jpg#1 - add change 3 (binary+F)
22+
[REDACTED]/build-pong-game.act#2 - edit change 4 (text)
23+
[REDACTED]/tutorial-readme.md#1 - add change 3 (text)

tests_e2e/tests_e2e.py

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@
66
These scripts contain "#! test <command>" comments.
77
They are expanded, executed and then diffed against the output in
88
"tests/integrations/references".
9+
10+
Platform-specific tests:
11+
Scripts suffixed with _linux, _darwin, or _windows (e.g., docker-alpine_linux.sh)
12+
only run on that platform. Scripts without a suffix run on all platforms.
13+
14+
Build tags (p4):
15+
Some nodes require Go build tags to be compiled in. P4 nodes (core/p4-run@v1,
16+
core/p4-sync@v1, etc.) are guarded by `//go:build p4` and require the P4 API SDK.
17+
The test runner auto-detects the P4 API SDK in `p4api/` and builds with `-tags=p4`
18+
when available. Run `bash setup.sh` to download the SDK if not present.
19+
20+
To add a new P4 e2e test:
21+
1. Run `bash setup.sh` to download the P4 API SDK (if not already done)
22+
2. Create a .sh script in tests_e2e/scripts/ (no platform suffix = runs everywhere)
23+
3. Set P4 env vars (P4PORT, P4USER, P4PASSWD) and use `#! test actrun <graph_file>`
24+
4. Run `python tests_e2e/tests_e2e.py p4_connect.sh` to generate the reference file
25+
5. Commit the reference file — it must match output for the tagged build in CI
26+
6. See tests_e2e/scripts/p4_connect.sh for an example
927
"""
1028

1129
import os
@@ -297,21 +315,96 @@ def process_and_run_test(root_dir: str, source_script: str, ref_dir: str, cov_di
297315
normalize_stack_trace_lines(ref_dir, script_name)
298316
return output.getvalue(), result.returncode == 0
299317

318+
def get_p4_build_config() -> dict | None:
319+
"""Detect P4 API SDK and return CGO flags for building with p4 tag.
320+
Returns None if P4 SDK is not available."""
321+
p4api_dir = Path(os.getcwd()) / "p4api"
322+
if not p4api_dir.exists():
323+
return None
324+
325+
p4_include = str(p4api_dir / "include")
326+
arch = platform.machine().lower()
327+
if arch in ("aarch64", "arm64"):
328+
arch = "arm64"
329+
else:
330+
arch = "x64"
331+
332+
if sys.platform == "linux":
333+
lib_name = "linux-aarch64" if arch == "arm64" else "linux-x86_64"
334+
p4_lib = str(p4api_dir / lib_name / "lib")
335+
ssl_lib = str(p4api_dir / f"ssl-linux-{arch}" / "lib")
336+
if not Path(p4_lib).exists():
337+
return None
338+
cgo_cppflags = f"-I{p4_include}"
339+
if Path(ssl_lib).exists():
340+
cgo_ldflags = f"-L{p4_lib} -lp4api {ssl_lib}/libssl.a {ssl_lib}/libcrypto.a"
341+
else:
342+
cgo_ldflags = f"-L{p4_lib} -lp4api -lssl -lcrypto"
343+
elif sys.platform == "darwin":
344+
p4_lib = str(p4api_dir / "macos" / "lib")
345+
ssl_lib = str(p4api_dir / f"ssl-macos-{arch}" / "lib")
346+
if not Path(p4_lib).exists():
347+
return None
348+
cgo_cppflags = f"-I{p4_include}"
349+
cgo_ldflags = f"-L{p4_lib} -lp4api {ssl_lib}/libssl.a {ssl_lib}/libcrypto.a -framework ApplicationServices -framework Foundation -framework Security -framework CoreFoundation"
350+
elif sys.platform == "win32":
351+
if arch == "arm64":
352+
return None # P4 not supported on Windows ARM64
353+
p4_lib = str(p4api_dir / "windows-x86_64" / "lib")
354+
if not Path(p4_lib).exists():
355+
return None
356+
cgo_cppflags = f"-I{p4_include} -DOS_NT"
357+
# Try to find static OpenSSL libs from MSYS2 MinGW
358+
ssl_lib = str(p4api_dir / "ssl-windows-x64" / "lib")
359+
if not Path(ssl_lib).exists():
360+
# Fall back to MSYS2 MinGW installation path
361+
msys2_lib = Path(os.environ.get("MSYS2_ROOT", r"D:\a\_temp\msys64")) / "mingw64" / "lib"
362+
if (msys2_lib / "libssl.a").exists():
363+
ssl_lib = str(msys2_lib)
364+
if Path(ssl_lib).exists() and (Path(ssl_lib) / "libssl.a").exists():
365+
cgo_ldflags = f"-L{p4_lib} -lp4api {ssl_lib}/libssl.a {ssl_lib}/libcrypto.a -lcrypt32 -lws2_32 -lole32 -lshell32 -luser32 -ladvapi32"
366+
else:
367+
cgo_ldflags = f"-L{p4_lib} -lp4api -lssl -lcrypto -lcrypt32 -lws2_32 -lole32 -lshell32 -luser32 -ladvapi32"
368+
else:
369+
return None
370+
371+
return {
372+
"CGO_ENABLED": "1",
373+
"CGO_CPPFLAGS": cgo_cppflags,
374+
"CGO_LDFLAGS": cgo_ldflags,
375+
}
376+
377+
300378
def compile_binaries(is_github_runner: bool):
301379
if is_github_runner:
302380
return
303381

304382
# build CLI
305383
cli_out = 'dist/actrun' + ('.exe' if IS_WINDOWS else '')
306-
384+
307385
env = GLOBAL_ENVS.copy()
308386
env["GCFLAGS"] = "-N -l"
309-
310-
build_cmd = ['go', 'build', '-o', cli_out, '.']
387+
388+
# Detect P4 API SDK and add build tag + CGO flags if available
389+
tags = []
390+
p4_config = get_p4_build_config()
391+
if p4_config:
392+
tags.append("p4")
393+
env.update(p4_config)
394+
print(f"P4 API SDK detected, building with -tags=p4")
395+
else:
396+
print(f"P4 API SDK not found, building without p4 support")
397+
398+
build_cmd = ['go', 'build']
399+
if tags:
400+
build_cmd.append(f'-tags={",".join(tags)}')
401+
build_cmd.extend(['-o', cli_out, '.'])
402+
311403
if COVERAGE:
312404
# TODO: (Seb) coverage build takes ages
313-
build_cmd = ['go', 'test', '.', '-buildvcs=true', '-cover', '-coverprofile', '-tags=main_test', '-c', '-o', cli_out]
314-
405+
coverage_tags = ['main_test'] + tags
406+
build_cmd = ['go', 'test', '.', '-buildvcs=true', '-cover', '-coverprofile', f'-tags={",".join(coverage_tags)}', '-c', '-o', cli_out]
407+
315408
print(f"Building {cli_out}")
316409
subprocess.run(build_cmd, stdout=sys.stdout, stderr=subprocess.STDOUT, check=True, env=env)
317410

0 commit comments

Comments
 (0)