Skip to content

Commit

Permalink
feat(kurtosis-devnet): use inspect as a data source
Browse files Browse the repository at this point in the history
This enables us to retrieve information about artifacts and services,
for downstream consumption.
  • Loading branch information
sigma committed Dec 19, 2024
1 parent ea5d830 commit d89d0a2
Show file tree
Hide file tree
Showing 2 changed files with 341 additions and 0 deletions.
165 changes: 165 additions & 0 deletions kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package inspect

import (
"bufio"
"fmt"
"io"
"strings"
)

type PortMap map[string]int

type ServiceMap map[string]PortMap

// InspectData represents the parsed output of "kurtosis enclave inspect"
type InspectData struct {
FileArtifacts []string
UserServices ServiceMap
}

type Inspector struct{}

type InspectorOption func(*Inspector)

func NewInspector(opts ...InspectorOption) *Inspector {
e := &Inspector{}
for _, opt := range opts {
opt(e)
}
return e
}

// extractPortName extracts the port name from the left part of a port mapping
func extractPortName(leftPart string) string {
if strings.Contains(leftPart, ":") {
lastColonIndex := strings.LastIndex(leftPart, ":")
return strings.TrimSpace(leftPart[:lastColonIndex])
}

fields := strings.Fields(leftPart)
if len(fields) > 0 {
return fields[0]
}
return ""
}

// extractPort extracts the port number from the right part of a port mapping
// TODO: this is a bit of a hack, but it works for now. It'll probably break
// once we start using the k8s backend, as the IPs will become important.
func extractPort(rightPart string) (int, error) {
rightPart = strings.TrimSpace(rightPart)
rightPart = strings.TrimPrefix(rightPart, "http://")
if !strings.HasPrefix(rightPart, "127.0.0.1:") {
return 0, fmt.Errorf("invalid port mapping format")
}

portStr := strings.TrimPrefix(rightPart, "127.0.0.1:")
var port int
_, err := fmt.Sscanf(portStr, "%d", &port)
return port, err
}

// parsePortMapping parses a port mapping string and adds it to the result
func parsePortMapping(line string, currentService string, result *InspectData) {
parts := strings.Split(line, "->")
if len(parts) < 2 {
return
}

leftPart := strings.TrimRight(parts[0], " \t")

portName := extractPortName(leftPart)
if portName == "" {
return
}

port, err := extractPort(parts[1])
if err == nil && currentService != "" {
result.UserServices[currentService][portName] = port
}
}

// ExtractData parses the output of "kurtosis enclave inspect" command
// TODO: we DEFINITELY need to move this to kurtosis SDK.
func (e *Inspector) ExtractData(r io.Reader) (*InspectData, error) {
result := &InspectData{
FileArtifacts: make([]string, 0),
UserServices: make(ServiceMap),
}

scanner := bufio.NewScanner(r)

// States for parsing different sections
const (
None = iota
Files
Services
)

state := None
var currentService string

for scanner.Scan() {
line := scanner.Text()
// Only trim for section detection
trimmedLine := strings.TrimSpace(line)

if trimmedLine == "" {
continue
}

// Check section headers using trimmed line
if strings.Contains(trimmedLine, "Files Artifacts") {
state = Files
continue
}
if strings.Contains(trimmedLine, "User Services") {
state = Services
continue
}

// Skip header lines
if strings.HasPrefix(trimmedLine, "UUID") || strings.HasPrefix(trimmedLine, "====") {
continue
}

switch state {
case Files:
fields := strings.Fields(trimmedLine)
if len(fields) >= 2 {
result.FileArtifacts = append(result.FileArtifacts, fields[1])
}

case Services:
fields := strings.Fields(trimmedLine)
if len(fields) == 0 {
continue
}

// If line starts with UUID, it's a new service
if len(fields) >= 2 && len(fields[0]) == 12 {
currentService = fields[1]
result.UserServices[currentService] = make(map[string]int)

// Check if there's a port mapping on the same line
if strings.Contains(line, "->") {
// Find the position after the service name
serviceNameEnd := strings.Index(line, currentService) + len(currentService)
// Process the rest of the line for port mapping
portLine := line[serviceNameEnd:]
if strings.Contains(portLine, "->") {
parsePortMapping(portLine, currentService, result)
}
}
} else if strings.Contains(line, "->") {
parsePortMapping(line, currentService, result)
}
}
}

if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning output: %w", err)
}

return result, nil
}
176 changes: 176 additions & 0 deletions kurtosis-devnet/pkg/kurtosis/sources/inspect/inspect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package inspect

import (
"strings"
"testing"
)

func TestParseInspectOutput(t *testing.T) {
output := `Name: interop-devnet
UUID: 1aca207b7bfd
Status: RUNNING
Creation Time: Mon, 16 Dec 2024 21:43:28 CET
Flags:
========================================= Files Artifacts =========================================
UUID Name
24fa22fbbe9e 1-lighthouse-geth-0-63
018a906c5ea5 el_cl_genesis_data
7a52f4b6848f final-genesis-timestamp
1dfce39e2be9 genesis-el-cl-env-file
49805cf85754 genesis_validators_root
02ea3e61386e jwt_file
19d0b8addd06 keymanager_file
233da3830dd2 op-deployer-configs
15b859be0607 op-deployer-fund-script
27127fc07627 op_jwt_fileop-kurtosis-1
b6740ec44fb2 op_jwt_fileop-kurtosis-2
5ce33ff4e9ef prysm-password
550585a62aa7 validator-ranges
========================================== User Services ==========================================
UUID Name Ports Status
295ece6f10b0 cl-1-lighthouse-geth http: 4000/tcp -> http://127.0.0.1:56397 RUNNING
metrics: 5054/tcp -> http://127.0.0.1:56398
tcp-discovery: 9000/tcp -> 127.0.0.1:56399
udp-discovery: 9000/udp -> 127.0.0.1:50029
d8010602c8d9 el-1-geth-lighthouse engine-rpc: 8551/tcp -> 127.0.0.1:56384 RUNNING
metrics: 9001/tcp -> http://127.0.0.1:56385
rpc: 8545/tcp -> 127.0.0.1:56382
tcp-discovery: 30303/tcp -> 127.0.0.1:56381
udp-discovery: 30303/udp -> 127.0.0.1:50818
ws: 8546/tcp -> 127.0.0.1:56383
cea9c515cc61 op-batcher-op-kurtosis-1 http: 8548/tcp -> http://127.0.0.1:56772 RUNNING
0d0dea3a7281 op-batcher-op-kurtosis-2 http: 8548/tcp -> http://127.0.0.1:57052 RUNNING
108409b50fc1 op-cl-1-op-node-op-geth-op-kurtosis-1 http: 8547/tcp -> http://127.0.0.1:56752 RUNNING
tcp-discovery: 9003/tcp -> 127.0.0.1:56753
udp-discovery: 9003/udp -> 127.0.0.1:61159
a5392ca8849f op-cl-1-op-node-op-geth-op-kurtosis-2 http: 8547/tcp -> http://127.0.0.1:56901 RUNNING
tcp-discovery: 9003/tcp -> 127.0.0.1:56902
udp-discovery: 9003/udp -> 127.0.0.1:58904
93128de6641b op-el-1-op-geth-op-node-op-kurtosis-1 engine-rpc: 8551/tcp -> 127.0.0.1:56734 RUNNING
metrics: 9001/tcp -> 127.0.0.1:56735
rpc: 8545/tcp -> http://127.0.0.1:56732
tcp-discovery: 30303/tcp -> 127.0.0.1:56731
udp-discovery: 30303/udp -> 127.0.0.1:64848
ws: 8546/tcp -> 127.0.0.1:56733
884fca1b00ad op-el-1-op-geth-op-node-op-kurtosis-2 engine-rpc: 8551/tcp -> 127.0.0.1:56786 RUNNING
metrics: 9001/tcp -> 127.0.0.1:56787
rpc: 8545/tcp -> http://127.0.0.1:56784
tcp-discovery: 30303/tcp -> 127.0.0.1:56783
udp-discovery: 30303/udp -> 127.0.0.1:52005
ws: 8546/tcp -> 127.0.0.1:56785
a75ce8815bea validator-key-generation-cl-validator-keystore <none> RUNNING
155a7d9a065d vc-1-geth-lighthouse metrics: 8080/tcp -> http://127.0.0.1:56408 RUNNING
`

result, err := NewInspector().ExtractData(strings.NewReader(output))
if err != nil {
t.Fatalf("Failed to parse inspect output: %v", err)
}

// Verify file artifacts
expectedFiles := []string{
"1-lighthouse-geth-0-63",
"el_cl_genesis_data",
"final-genesis-timestamp",
"genesis-el-cl-env-file",
"genesis_validators_root",
"jwt_file",
"keymanager_file",
"op-deployer-configs",
"op-deployer-fund-script",
"op_jwt_fileop-kurtosis-1",
"op_jwt_fileop-kurtosis-2",
"prysm-password",
"validator-ranges",
}

if len(result.FileArtifacts) != len(expectedFiles) {
t.Errorf("Expected %d file artifacts, got %d", len(expectedFiles), len(result.FileArtifacts))
}

for i, file := range expectedFiles {
if i >= len(result.FileArtifacts) {
t.Errorf("Missing expected file artifact: %s", file)
continue
}
if result.FileArtifacts[i] != file {
t.Errorf("Expected file artifact %s, got %s", file, result.FileArtifacts[i])
}
}

// Verify services and ports
expectedServices := map[string]map[string]int{
"cl-1-lighthouse-geth": {
"http": 56397,
"metrics": 56398,
"tcp-discovery": 56399,
"udp-discovery": 50029,
},
"el-1-geth-lighthouse": {
"engine-rpc": 56384,
"metrics": 56385,
"rpc": 56382,
"tcp-discovery": 56381,
"udp-discovery": 50818,
"ws": 56383,
},
"op-batcher-op-kurtosis-1": {
"http": 56772,
},
"op-batcher-op-kurtosis-2": {
"http": 57052,
},
"op-cl-1-op-node-op-geth-op-kurtosis-1": {
"http": 56752,
"tcp-discovery": 56753,
"udp-discovery": 61159,
},
"op-cl-1-op-node-op-geth-op-kurtosis-2": {
"http": 56901,
"tcp-discovery": 56902,
"udp-discovery": 58904,
},
"op-el-1-op-geth-op-node-op-kurtosis-1": {
"engine-rpc": 56734,
"metrics": 56735,
"rpc": 56732,
"tcp-discovery": 56731,
"udp-discovery": 64848,
"ws": 56733,
},
"op-el-1-op-geth-op-node-op-kurtosis-2": {
"engine-rpc": 56786,
"metrics": 56787,
"rpc": 56784,
"tcp-discovery": 56783,
"udp-discovery": 52005,
"ws": 56785,
},
"validator-key-generation-cl-validator-keystore": {},
"vc-1-geth-lighthouse": {
"metrics": 56408,
},
}

for service, expectedPorts := range expectedServices {
ports, exists := result.UserServices[service]
if !exists {
t.Errorf("Expected service %s not found", service)
continue
}

for portName, expectedPort := range expectedPorts {
actualPort, exists := ports[portName]
if !exists {
t.Errorf("Expected port %s not found for service %s", portName, service)
continue
}
if actualPort != expectedPort {
t.Errorf("For service %s port %s: expected port %d, got %d",
service, portName, expectedPort, actualPort)
}
}
}
}

0 comments on commit d89d0a2

Please sign in to comment.