Commit dedf3452 authored by Yann Hodique's avatar Yann Hodique Committed by GitHub

feat(kurtosis-devnet): refactor run logic (#13554)

This change reimplements run over the kurtosis SDK instead of shelling
out to the CLI tool, which fixes potential consistency problems with
other uses of the SDK.

Additionally, we split the endpoint detection logic into a separate
entity (will make it a separate package a bit later), and improve the
host handling part by no longer assuming reachability on localhost
(implicitly assuming Docker backend). This is a step toward supporting
k8s backend transparently.
parent dd7f0fd2
...@@ -17,6 +17,7 @@ require ( ...@@ -17,6 +17,7 @@ require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20241213092551-33a63fce8214
github.com/ethereum/go-ethereum v1.14.11 github.com/ethereum/go-ethereum v1.14.11
github.com/fatih/color v1.16.0
github.com/fsnotify/fsnotify v1.8.0 github.com/fsnotify/fsnotify v1.8.0
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
...@@ -96,7 +97,6 @@ require ( ...@@ -96,7 +97,6 @@ require (
github.com/elastic/gosigar v0.14.3 // indirect github.com/elastic/gosigar v0.14.3 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/fgprof v0.9.5 // indirect
github.com/ferranbt/fastssz v0.1.2 // indirect github.com/ferranbt/fastssz v0.1.2 // indirect
github.com/flynn/noise v1.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect
......
package kurtosis
import (
"fmt"
"strconv"
"strings"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect"
)
// ServiceFinder is the main entry point for finding service endpoints
type ServiceFinder struct {
services inspect.ServiceMap
nodeServices []string
interestingPorts []string
l2ServicePrefix string
}
// ServiceFinderOption configures a ServiceFinder
type ServiceFinderOption func(*ServiceFinder)
// WithNodeServices sets the node service identifiers
func WithNodeServices(services []string) ServiceFinderOption {
return func(f *ServiceFinder) {
f.nodeServices = services
}
}
// WithInterestingPorts sets the ports to look for
func WithInterestingPorts(ports []string) ServiceFinderOption {
return func(f *ServiceFinder) {
f.interestingPorts = ports
}
}
// WithL2ServicePrefix sets the prefix used to identify L2 services
func WithL2ServicePrefix(prefix string) ServiceFinderOption {
return func(f *ServiceFinder) {
f.l2ServicePrefix = prefix
}
}
// NewServiceFinder creates a new ServiceFinder with the given options
func NewServiceFinder(services inspect.ServiceMap, opts ...ServiceFinderOption) *ServiceFinder {
f := &ServiceFinder{
services: services,
nodeServices: []string{"cl", "el"},
interestingPorts: []string{"rpc", "http"},
l2ServicePrefix: "op-",
}
for _, opt := range opts {
opt(f)
}
return f
}
// FindL1Endpoints finds L1 nodes. Currently returns empty endpoints as specified.
func (f *ServiceFinder) FindL1Endpoints() ([]Node, EndpointMap) {
return f.findRPCEndpoints(func(serviceName string) (string, int, bool) {
// Only match services that start with one of the node service identifiers.
// We might have to change this if we need to support L1 services beyond nodes.
for _, service := range f.nodeServices {
if strings.HasPrefix(serviceName, service) {
tag, idx := f.serviceTag(serviceName)
return tag, idx, true
}
}
return "", 0, false
})
}
// FindL2Endpoints finds L2 nodes and endpoints for a specific network
func (f *ServiceFinder) FindL2Endpoints(network string) ([]Node, EndpointMap) {
networkSuffix := "-" + network
return f.findRPCEndpoints(func(serviceName string) (string, int, bool) {
if strings.HasSuffix(serviceName, networkSuffix) {
name := strings.TrimSuffix(serviceName, networkSuffix)
tag, idx := f.serviceTag(strings.TrimPrefix(name, f.l2ServicePrefix))
return tag, idx, true
}
return "", 0, false
})
}
// findRPCEndpoints looks for services matching the given predicate that have an RPC port
func (f *ServiceFinder) findRPCEndpoints(matchService func(string) (string, int, bool)) ([]Node, EndpointMap) {
endpointMap := make(EndpointMap)
var nodes []Node
for serviceName, ports := range f.services {
var portInfo *inspect.PortInfo
for _, interestingPort := range f.interestingPorts {
if p, ok := ports[interestingPort]; ok {
portInfo = &p
break
}
}
if portInfo == nil {
continue
}
if serviceIdentifier, num, ok := matchService(serviceName); ok {
var allocated bool
for _, service := range f.nodeServices {
if serviceIdentifier == service {
if num > len(nodes) {
nodes = append(nodes, make(Node))
}
host := portInfo.Host
if host == "" {
host = "localhost"
}
nodes[num-1][serviceIdentifier] = fmt.Sprintf("http://%s:%d", host, portInfo.Port)
allocated = true
}
}
if !allocated {
host := portInfo.Host
if host == "" {
host = "localhost"
}
endpointMap[serviceIdentifier] = fmt.Sprintf("http://%s:%d", host, portInfo.Port)
}
}
}
return nodes, endpointMap
}
// serviceTag returns the shorthand service tag and index if it's a service with multiple instances
func (f *ServiceFinder) serviceTag(serviceName string) (string, int) {
// Find start of number sequence
start := strings.IndexFunc(serviceName, func(r rune) bool {
return r >= '0' && r <= '9'
})
if start == -1 {
return serviceName, 0
}
// Find end of number sequence
end := start + 1
for end < len(serviceName) && serviceName[end] >= '0' && serviceName[end] <= '9' {
end++
}
idx, err := strconv.Atoi(serviceName[start:end])
if err != nil {
return serviceName, 0
}
return serviceName[:start-1], idx
}
package kurtosis
import (
"testing"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect"
"github.com/stretchr/testify/assert"
)
func TestFindRPCEndpoints(t *testing.T) {
testServices := make(inspect.ServiceMap)
testServices["el-1-geth-lighthouse"] = inspect.PortMap{
"metrics": {Port: 52643},
"tcp-discovery": {Port: 52644},
"udp-discovery": {Port: 51936},
"engine-rpc": {Port: 52642},
"rpc": {Port: 52645},
"ws": {Port: 52646},
}
testServices["op-batcher-op-kurtosis"] = inspect.PortMap{
"http": {Port: 53572},
}
testServices["op-cl-1-op-node-op-geth-op-kurtosis"] = inspect.PortMap{
"udp-discovery": {Port: 50990},
"http": {Port: 53503},
"tcp-discovery": {Port: 53504},
}
testServices["op-el-1-op-geth-op-node-op-kurtosis"] = inspect.PortMap{
"udp-discovery": {Port: 53233},
"engine-rpc": {Port: 53399},
"metrics": {Port: 53400},
"rpc": {Port: 53402},
"ws": {Port: 53403},
"tcp-discovery": {Port: 53401},
}
testServices["vc-1-geth-lighthouse"] = inspect.PortMap{
"metrics": {Port: 53149},
}
testServices["cl-1-lighthouse-geth"] = inspect.PortMap{
"metrics": {Port: 52691},
"tcp-discovery": {Port: 52692},
"udp-discovery": {Port: 58275},
"http": {Port: 52693},
}
tests := []struct {
name string
services inspect.ServiceMap
findFn func(*ServiceFinder) ([]Node, EndpointMap)
wantNodes []Node
wantEndpoints EndpointMap
}{
{
name: "find L1 endpoints",
services: testServices,
findFn: func(f *ServiceFinder) ([]Node, EndpointMap) {
return f.FindL1Endpoints()
},
wantNodes: []Node{
{
"cl": "http://localhost:52693",
"el": "http://localhost:52645",
},
},
wantEndpoints: EndpointMap{},
},
{
name: "find op-kurtosis L2 endpoints",
services: testServices,
findFn: func(f *ServiceFinder) ([]Node, EndpointMap) {
return f.FindL2Endpoints("op-kurtosis")
},
wantNodes: []Node{
{
"cl": "http://localhost:53503",
"el": "http://localhost:53402",
},
},
wantEndpoints: EndpointMap{
"batcher": "http://localhost:53572",
},
},
{
name: "custom host in endpoint",
services: inspect.ServiceMap{
"op-batcher-custom-host": inspect.PortMap{
"http": {Host: "custom.host", Port: 8080},
},
},
findFn: func(f *ServiceFinder) ([]Node, EndpointMap) {
return f.FindL2Endpoints("custom-host")
},
wantNodes: nil,
wantEndpoints: EndpointMap{
"batcher": "http://custom.host:8080",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
finder := NewServiceFinder(tt.services)
gotNodes, gotEndpoints := tt.findFn(finder)
assert.Equal(t, tt.wantNodes, gotNodes)
assert.Equal(t, tt.wantEndpoints, gotEndpoints)
})
}
}
...@@ -6,13 +6,8 @@ import ( ...@@ -6,13 +6,8 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"strconv"
"strings"
"text/template"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec"
) )
...@@ -51,8 +46,6 @@ type KurtosisEnvironment struct { ...@@ -51,8 +46,6 @@ type KurtosisEnvironment struct {
type KurtosisDeployer struct { type KurtosisDeployer struct {
// Base directory where the deployment commands should be executed // Base directory where the deployment commands should be executed
baseDir string baseDir string
// Template for the deployment command
cmdTemplate *template.Template
// Package name to deploy // Package name to deploy
packageName string packageName string
// Dry run mode // Dry run mode
...@@ -63,14 +56,8 @@ type KurtosisDeployer struct { ...@@ -63,14 +56,8 @@ type KurtosisDeployer struct {
enclaveSpec EnclaveSpecifier enclaveSpec EnclaveSpecifier
enclaveInspecter EnclaveInspecter enclaveInspecter EnclaveInspecter
enclaveObserver EnclaveObserver enclaveObserver EnclaveObserver
} kurtosisCtx kurtosisContextInterface
runHandlers []MessageHandler
const cmdTemplateStr = "just _kurtosis-run {{.PackageName}} {{.ArgFile}} {{.Enclave}}"
var defaultCmdTemplate *template.Template
func init() {
defaultCmdTemplate = template.Must(template.New("kurtosis_deploy_cmd").Parse(cmdTemplateStr))
} }
type KurtosisDeployerOptions func(*KurtosisDeployer) type KurtosisDeployerOptions func(*KurtosisDeployer)
...@@ -81,12 +68,6 @@ func WithKurtosisBaseDir(baseDir string) KurtosisDeployerOptions { ...@@ -81,12 +68,6 @@ func WithKurtosisBaseDir(baseDir string) KurtosisDeployerOptions {
} }
} }
func WithKurtosisCmdTemplate(cmdTemplate *template.Template) KurtosisDeployerOptions {
return func(d *KurtosisDeployer) {
d.cmdTemplate = cmdTemplate
}
}
func WithKurtosisPackageName(packageName string) KurtosisDeployerOptions { func WithKurtosisPackageName(packageName string) KurtosisDeployerOptions {
return func(d *KurtosisDeployer) { return func(d *KurtosisDeployer) {
d.packageName = packageName d.packageName = packageName
...@@ -123,14 +104,19 @@ func WithKurtosisEnclaveObserver(enclaveObserver EnclaveObserver) KurtosisDeploy ...@@ -123,14 +104,19 @@ func WithKurtosisEnclaveObserver(enclaveObserver EnclaveObserver) KurtosisDeploy
} }
} }
func WithKurtosisRunHandlers(runHandlers []MessageHandler) KurtosisDeployerOptions {
return func(d *KurtosisDeployer) {
d.runHandlers = runHandlers
}
}
// NewKurtosisDeployer creates a new KurtosisDeployer instance // NewKurtosisDeployer creates a new KurtosisDeployer instance
func NewKurtosisDeployer(opts ...KurtosisDeployerOptions) *KurtosisDeployer { func NewKurtosisDeployer(opts ...KurtosisDeployerOptions) *KurtosisDeployer {
d := &KurtosisDeployer{ d := &KurtosisDeployer{
baseDir: ".", baseDir: ".",
cmdTemplate: defaultCmdTemplate,
packageName: DefaultPackageName, packageName: DefaultPackageName,
dryRun: false, dryRun: false,
enclave: "devnet", enclave: DefaultEnclave,
enclaveSpec: &enclaveSpecAdapter{}, enclaveSpec: &enclaveSpecAdapter{},
enclaveInspecter: &enclaveInspectAdapter{}, enclaveInspecter: &enclaveInspectAdapter{},
...@@ -144,99 +130,6 @@ func NewKurtosisDeployer(opts ...KurtosisDeployerOptions) *KurtosisDeployer { ...@@ -144,99 +130,6 @@ func NewKurtosisDeployer(opts ...KurtosisDeployerOptions) *KurtosisDeployer {
return d return d
} }
// templateData holds the data for the command template
type templateData struct {
PackageName string
ArgFile string
Enclave string
}
// TODO: the following functions follow closely the naming convensions in place
// with optimism-package. We should probably make them a bit more generic, to
// make the whole thing less fragile.
// findRPCEndpoint looks for a service matching the given predicate that has an RPC port
func findRPCEndpoints(services inspect.ServiceMap, matchService func(string) (string, int, bool)) ([]Node, EndpointMap) {
interestingPorts := []string{"rpc", "http"}
nodeServices := []string{"cl", "el"}
endpointMap := make(EndpointMap)
var nodes []Node
for serviceName, ports := range services {
var port int
for _, interestingPort := range interestingPorts {
if p, ok := ports[interestingPort]; ok {
port = p
break
}
}
if port == 0 { // nothing to see here
continue
}
if serviceIdentifier, num, ok := matchService(serviceName); ok {
var allocated bool
for _, service := range nodeServices {
if serviceIdentifier == service { // this is a node
if num > len(nodes) {
nodes = append(nodes, make(Node))
}
nodes[num-1][serviceIdentifier] = fmt.Sprintf("http://localhost:%d", port)
allocated = true
}
}
if !allocated {
endpointMap[serviceIdentifier] = fmt.Sprintf("http://localhost:%d", port)
}
}
}
return nodes, endpointMap
}
// return the shorthand service tag (used as key in the final output) and the
// index if that's a service with multiple instances.
func serviceTag(serviceName string) (string, int) {
// Find index of first number
i := strings.IndexFunc(serviceName, func(r rune) bool {
return r >= '0' && r <= '9'
})
if i == -1 {
return serviceName, 0
}
idx, err := strconv.Atoi(serviceName[i : i+1])
if err != nil {
return serviceName, 0
}
return serviceName[:i-1], idx
}
const l2ServiceTagPrefix = "op-"
func findL2Endpoints(services inspect.ServiceMap, suffix string) ([]Node, EndpointMap) {
return findRPCEndpoints(services, func(serviceName string) (string, int, bool) {
if strings.HasSuffix(serviceName, suffix) {
name := strings.TrimSuffix(serviceName, suffix)
tag, idx := serviceTag(strings.TrimPrefix(name, l2ServiceTagPrefix))
return tag, idx, true
}
return "", 0, false
})
}
// TODO: L1 services are detected as "non-L2" right now. That might need to change
// in the future, but for now it's good enough.
func findL1Endpoints(services inspect.ServiceMap) ([]Node, EndpointMap) {
return findRPCEndpoints(services, func(serviceName string) (string, int, bool) {
match := !strings.HasPrefix(serviceName, l2ServiceTagPrefix)
if match {
tag, idx := serviceTag(serviceName)
return tag, idx, true
}
return "", 0, false
})
}
// prepareArgFile creates a temporary file with the input content and returns its path // prepareArgFile creates a temporary file with the input content and returns its path
// The caller is responsible for deleting the file. // The caller is responsible for deleting the file.
func (d *KurtosisDeployer) prepareArgFile(input io.Reader) (string, error) { func (d *KurtosisDeployer) prepareArgFile(input io.Reader) (string, error) {
...@@ -254,38 +147,6 @@ func (d *KurtosisDeployer) prepareArgFile(input io.Reader) (string, error) { ...@@ -254,38 +147,6 @@ func (d *KurtosisDeployer) prepareArgFile(input io.Reader) (string, error) {
return argFile.Name(), nil return argFile.Name(), nil
} }
// runKurtosisCommand executes the kurtosis command with the given arguments
// TODO: reimplement this with the kurtosis SDK, it'll be cleaner.
func (d *KurtosisDeployer) runKurtosisCommand(argFile string) error {
data := templateData{
PackageName: d.packageName,
ArgFile: argFile,
Enclave: d.enclave,
}
var cmdBuf bytes.Buffer
if err := d.cmdTemplate.Execute(&cmdBuf, data); err != nil {
return fmt.Errorf("failed to execute command template: %w", err)
}
if d.dryRun {
fmt.Println("Dry run mode enabled, kurtosis would run the following command:")
fmt.Println(cmdBuf.String())
return nil
}
cmd := exec.Command("sh", "-c", cmdBuf.String())
cmd.Dir = d.baseDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("kurtosis deployment failed: %w", err)
}
return nil
}
func (d *KurtosisDeployer) getWallets(wallets deployer.WalletList) WalletMap { func (d *KurtosisDeployer) getWallets(wallets deployer.WalletList) WalletMap {
walletMap := make(WalletMap) walletMap := make(WalletMap)
for _, wallet := range wallets { for _, wallet := range wallets {
...@@ -316,7 +177,8 @@ func (d *KurtosisDeployer) getEnvironmentInfo(ctx context.Context, spec *spec.En ...@@ -316,7 +177,8 @@ func (d *KurtosisDeployer) getEnvironmentInfo(ctx context.Context, spec *spec.En
} }
// Find L1 endpoint // Find L1 endpoint
if nodes, endpoints := findL1Endpoints(inspectResult.UserServices); len(nodes) > 0 { finder := NewServiceFinder(inspectResult.UserServices)
if nodes, endpoints := finder.FindL1Endpoints(); len(nodes) > 0 {
env.L1 = &Chain{ env.L1 = &Chain{
Name: "Ethereum", Name: "Ethereum",
Services: endpoints, Services: endpoints,
...@@ -326,7 +188,7 @@ func (d *KurtosisDeployer) getEnvironmentInfo(ctx context.Context, spec *spec.En ...@@ -326,7 +188,7 @@ func (d *KurtosisDeployer) getEnvironmentInfo(ctx context.Context, spec *spec.En
// Find L2 endpoints // Find L2 endpoints
for _, chainSpec := range spec.Chains { for _, chainSpec := range spec.Chains {
nodes, endpoints := findL2Endpoints(inspectResult.UserServices, fmt.Sprintf("-%s", chainSpec.Name)) nodes, endpoints := finder.FindL2Endpoints(chainSpec.Name)
chain := &Chain{ chain := &Chain{
Name: chainSpec.Name, Name: chainSpec.Name,
...@@ -365,7 +227,7 @@ func (d *KurtosisDeployer) Deploy(ctx context.Context, input io.Reader) (*Kurtos ...@@ -365,7 +227,7 @@ func (d *KurtosisDeployer) Deploy(ctx context.Context, input io.Reader) (*Kurtos
defer os.Remove(argFile) defer os.Remove(argFile)
// Run kurtosis command // Run kurtosis command
if err := d.runKurtosisCommand(argFile); err != nil { if err := d.runKurtosis(ctx, argFile); err != nil {
return nil, err return nil, err
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"text/template"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/deployer"
"github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/inspect"
...@@ -16,89 +15,6 @@ import ( ...@@ -16,89 +15,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestFindRPCEndpoints(t *testing.T) {
testServices := inspect.ServiceMap{
"el-1-geth-lighthouse": {
"metrics": 52643,
"tcp-discovery": 52644,
"udp-discovery": 51936,
"engine-rpc": 52642,
"rpc": 52645,
"ws": 52646,
},
"op-batcher-op-kurtosis": {
"http": 53572,
},
"op-cl-1-op-node-op-geth-op-kurtosis": {
"udp-discovery": 50990,
"http": 53503,
"tcp-discovery": 53504,
},
"op-el-1-op-geth-op-node-op-kurtosis": {
"udp-discovery": 53233,
"engine-rpc": 53399,
"metrics": 53400,
"rpc": 53402,
"ws": 53403,
"tcp-discovery": 53401,
},
"vc-1-geth-lighthouse": {
"metrics": 53149,
},
"cl-1-lighthouse-geth": {
"metrics": 52691,
"tcp-discovery": 52692,
"udp-discovery": 58275,
"http": 52693,
},
}
tests := []struct {
name string
services inspect.ServiceMap
lookupFn func(inspect.ServiceMap) ([]Node, EndpointMap)
wantNodes []Node
wantEndpoints EndpointMap
}{
{
name: "find L1 endpoints",
services: testServices,
lookupFn: findL1Endpoints,
wantNodes: []Node{
{
"cl": "http://localhost:52693",
"el": "http://localhost:52645",
},
},
wantEndpoints: EndpointMap{},
},
{
name: "find op-kurtosis L2 endpoints",
services: testServices,
lookupFn: func(services inspect.ServiceMap) ([]Node, EndpointMap) {
return findL2Endpoints(services, "-op-kurtosis")
},
wantNodes: []Node{
{
"cl": "http://localhost:53503",
"el": "http://localhost:53402",
},
},
wantEndpoints: EndpointMap{
"batcher": "http://localhost:53572",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotNodes, gotEndpoints := tt.lookupFn(tt.services)
assert.Equal(t, tt.wantNodes, gotNodes)
assert.Equal(t, tt.wantEndpoints, gotEndpoints)
})
}
}
func TestKurtosisDeployer(t *testing.T) { func TestKurtosisDeployer(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
...@@ -158,53 +74,6 @@ func TestPrepareArgFile(t *testing.T) { ...@@ -158,53 +74,6 @@ func TestPrepareArgFile(t *testing.T) {
assert.Equal(t, "test content", string(content)) assert.Equal(t, "test content", string(content))
} }
func TestRunKurtosisCommand(t *testing.T) {
fakeCmdTemplate := template.Must(template.New("fake_cmd").Parse("echo 'would run: {{.PackageName}} {{.ArgFile}} {{.Enclave}}'"))
tests := []struct {
name string
dryRun bool
wantError bool
wantOutput bool
cmdTemplate *template.Template
}{
{
name: "dry run",
dryRun: true,
wantError: false,
cmdTemplate: fakeCmdTemplate,
},
{
name: "successful run",
dryRun: false,
wantError: false,
wantOutput: true,
cmdTemplate: fakeCmdTemplate,
},
{
name: "template error",
dryRun: false,
wantError: true,
cmdTemplate: template.Must(template.New("bad_cmd").Parse("{{.NonExistentField}}")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := NewKurtosisDeployer(
WithKurtosisDryRun(tt.dryRun),
WithKurtosisCmdTemplate(tt.cmdTemplate),
)
err := d.runKurtosisCommand("test.yaml")
if tt.wantError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
// fakeEnclaveInspecter implements EnclaveInspecter for testing // fakeEnclaveInspecter implements EnclaveInspecter for testing
type fakeEnclaveInspecter struct { type fakeEnclaveInspecter struct {
result *inspect.InspectData result *inspect.InspectData
...@@ -236,9 +105,6 @@ func (f *fakeEnclaveSpecifier) EnclaveSpec(r io.Reader) (*spec.EnclaveSpec, erro ...@@ -236,9 +105,6 @@ func (f *fakeEnclaveSpecifier) EnclaveSpec(r io.Reader) (*spec.EnclaveSpec, erro
} }
func TestDeploy(t *testing.T) { func TestDeploy(t *testing.T) {
// Create a template that just echoes the command that would be run
fakeCmdTemplate := template.Must(template.New("fake_cmd").Parse("echo 'would run: {{.PackageName}} {{.ArgFile}} {{.Enclave}}'"))
testSpecWithL2 := &spec.EnclaveSpec{ testSpecWithL2 := &spec.EnclaveSpec{
Chains: []spec.ChainSpec{ Chains: []spec.ChainSpec{
{ {
...@@ -252,19 +118,27 @@ func TestDeploy(t *testing.T) { ...@@ -252,19 +118,27 @@ func TestDeploy(t *testing.T) {
Chains: []spec.ChainSpec{}, Chains: []spec.ChainSpec{},
} }
testServices := inspect.ServiceMap{ // Define successful responses that will be used in multiple test cases
"el-1-geth-lighthouse": { successResponses := []fakeStarlarkResponse{
"rpc": 52645, {progressMsg: []string{"Starting deployment..."}},
}, {info: "Preparing environment"},
"op-el-1-op-geth-op-node-op-kurtosis": { {instruction: "Executing package"},
"rpc": 53402, {progressMsg: []string{"Deployment complete"}},
}, {isSuccessful: true},
"op-cl-1-op-node-op-geth-op-kurtosis": { }
"http": 53503,
}, testServices := make(inspect.ServiceMap)
"op-batcher-op-kurtosis": { testServices["el-1-geth-lighthouse"] = inspect.PortMap{
"http": 53572, "rpc": {Port: 52645},
}, }
testServices["op-el-1-op-geth-op-node-op-kurtosis"] = inspect.PortMap{
"rpc": {Port: 53402},
}
testServices["op-cl-1-op-node-op-geth-op-kurtosis"] = inspect.PortMap{
"http": {Port: 53503},
}
testServices["op-batcher-op-kurtosis"] = inspect.PortMap{
"http": {Port: 53572},
} }
testWallets := deployer.WalletList{ testWallets := deployer.WalletList{
...@@ -288,7 +162,8 @@ func TestDeploy(t *testing.T) { ...@@ -288,7 +162,8 @@ func TestDeploy(t *testing.T) {
inspectErr error inspectErr error
deployerState *deployer.DeployerData deployerState *deployer.DeployerData
deployerErr error deployerErr error
dryRun bool kurtosisErr error
responses []fakeStarlarkResponse
wantL1Nodes []Node wantL1Nodes []Node
wantL2Nodes []Node wantL2Nodes []Node
wantL2Services EndpointMap wantL2Services EndpointMap
...@@ -308,6 +183,7 @@ func TestDeploy(t *testing.T) { ...@@ -308,6 +183,7 @@ func TestDeploy(t *testing.T) {
"1234": testAddresses, "1234": testAddresses,
}, },
}, },
responses: successResponses,
wantL1Nodes: []Node{ wantL1Nodes: []Node{
{ {
"el": "http://localhost:52645", "el": "http://localhost:52645",
...@@ -336,12 +212,6 @@ func TestDeploy(t *testing.T) { ...@@ -336,12 +212,6 @@ func TestDeploy(t *testing.T) {
specErr: fmt.Errorf("spec failed"), specErr: fmt.Errorf("spec failed"),
wantErr: true, wantErr: true,
}, },
{
name: "dry run",
input: "test input",
spec: testSpecWithL2,
dryRun: true,
},
{ {
name: "inspect error", name: "inspect error",
input: "test input", input: "test input",
...@@ -349,6 +219,13 @@ func TestDeploy(t *testing.T) { ...@@ -349,6 +219,13 @@ func TestDeploy(t *testing.T) {
inspectErr: fmt.Errorf("inspect failed"), inspectErr: fmt.Errorf("inspect failed"),
wantErr: true, wantErr: true,
}, },
{
name: "kurtosis error",
input: "test input",
spec: testSpecWithL2,
kurtosisErr: fmt.Errorf("kurtosis failed"),
wantErr: true,
},
{ {
name: "deployer error", name: "deployer error",
input: "test input", input: "test input",
...@@ -365,11 +242,11 @@ func TestDeploy(t *testing.T) { ...@@ -365,11 +242,11 @@ func TestDeploy(t *testing.T) {
spec: testSpecWithL2, spec: testSpecWithL2,
inspectResult: &inspect.InspectData{ inspectResult: &inspect.InspectData{
UserServices: inspect.ServiceMap{ UserServices: inspect.ServiceMap{
"op-el-1-op-geth-op-node-op-kurtosis": { "op-el-1-op-geth-op-node-op-kurtosis": inspect.PortMap{
"rpc": 53402, "rpc": {Port: 53402},
}, },
"op-cl-1-op-node-op-geth-op-kurtosis": { "op-cl-1-op-node-op-geth-op-kurtosis": inspect.PortMap{
"http": 53503, "http": {Port: 53503},
}, },
}, },
}, },
...@@ -379,6 +256,7 @@ func TestDeploy(t *testing.T) { ...@@ -379,6 +256,7 @@ func TestDeploy(t *testing.T) {
"1234": testAddresses, "1234": testAddresses,
}, },
}, },
responses: successResponses,
wantL2Nodes: []Node{ wantL2Nodes: []Node{
{ {
"el": "http://localhost:53402", "el": "http://localhost:53402",
...@@ -398,14 +276,15 @@ func TestDeploy(t *testing.T) { ...@@ -398,14 +276,15 @@ func TestDeploy(t *testing.T) {
spec: testSpecNoL2, spec: testSpecNoL2,
inspectResult: &inspect.InspectData{ inspectResult: &inspect.InspectData{
UserServices: inspect.ServiceMap{ UserServices: inspect.ServiceMap{
"el-1-geth-lighthouse": { "el-1-geth-lighthouse": inspect.PortMap{
"rpc": 52645, "rpc": {Port: 52645},
}, },
}, },
}, },
deployerState: &deployer.DeployerData{ deployerState: &deployer.DeployerData{
Wallets: testWallets, Wallets: testWallets,
}, },
responses: successResponses,
wantL1Nodes: []Node{ wantL1Nodes: []Node{
{ {
"el": "http://localhost:52645", "el": "http://localhost:52645",
...@@ -422,9 +301,15 @@ func TestDeploy(t *testing.T) { ...@@ -422,9 +301,15 @@ func TestDeploy(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Create a fake Kurtosis context
fakeCtx := &fakeKurtosisContext{
enclaveCtx: &fakeEnclaveContext{
runErr: tt.kurtosisErr,
responses: tt.responses,
},
}
d := NewKurtosisDeployer( d := NewKurtosisDeployer(
WithKurtosisDryRun(tt.dryRun),
WithKurtosisCmdTemplate(fakeCmdTemplate),
WithKurtosisEnclaveSpec(&fakeEnclaveSpecifier{ WithKurtosisEnclaveSpec(&fakeEnclaveSpecifier{
spec: tt.spec, spec: tt.spec,
err: tt.specErr, err: tt.specErr,
...@@ -439,6 +324,9 @@ func TestDeploy(t *testing.T) { ...@@ -439,6 +324,9 @@ func TestDeploy(t *testing.T) {
}), }),
) )
// Set the fake Kurtosis context
d.kurtosisCtx = fakeCtx
env, err := d.Deploy(context.Background(), strings.NewReader(tt.input)) env, err := d.Deploy(context.Background(), strings.NewReader(tt.input))
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
...@@ -446,30 +334,25 @@ func TestDeploy(t *testing.T) { ...@@ -446,30 +334,25 @@ func TestDeploy(t *testing.T) {
} }
require.NoError(t, err) require.NoError(t, err)
if tt.dryRun { assert.NotNil(t, env)
assert.NotNil(t, env)
assert.Empty(t, env.L1)
assert.Empty(t, env.L2)
assert.Empty(t, env.Wallets)
return
}
if tt.wantL1Nodes != nil { if tt.wantL1Nodes != nil {
require.NotNil(t, env.L1)
assert.Equal(t, tt.wantL1Nodes, env.L1.Nodes) assert.Equal(t, tt.wantL1Nodes, env.L1.Nodes)
} else { } else {
assert.Nil(t, env.L1) assert.Nil(t, env.L1)
} }
if len(tt.wantL2Nodes) > 0 {
if tt.wantL2Nodes != nil {
require.Len(t, env.L2, 1)
assert.Equal(t, tt.wantL2Nodes, env.L2[0].Nodes) assert.Equal(t, tt.wantL2Nodes, env.L2[0].Nodes)
if tt.wantL2Services != nil { if tt.wantL2Services != nil {
assert.Equal(t, tt.wantL2Services, env.L2[0].Services) assert.Equal(t, tt.wantL2Services, env.L2[0].Services)
} }
if addresses, ok := tt.deployerState.State["1234"]; ok {
assert.Equal(t, addresses, env.L2[0].Addresses)
}
} else { } else {
assert.Empty(t, env.L2) assert.Empty(t, env.L2)
} }
assert.Equal(t, tt.wantWallets, env.Wallets) assert.Equal(t, tt.wantWallets, env.Wallets)
}) })
} }
......
...@@ -38,8 +38,12 @@ func main() { ...@@ -38,8 +38,12 @@ func main() {
fmt.Println("\nServices:") fmt.Println("\nServices:")
for svc, ports := range data.UserServices { for svc, ports := range data.UserServices {
fmt.Printf(" %s:\n", svc) fmt.Printf(" %s:\n", svc)
for portName, portNum := range ports { for portName, portInfo := range ports {
fmt.Printf(" %s: %d\n", portName, portNum) host := portInfo.Host
if host == "" {
host = "localhost"
}
fmt.Printf(" %s: %s:%d\n", portName, host, portInfo.Port)
} }
} }
} }
...@@ -6,7 +6,13 @@ import ( ...@@ -6,7 +6,13 @@ import (
"github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context"
) )
type PortMap map[string]int // PortInfo contains the host and port number for a service port
type PortInfo struct {
Host string
Port int
}
type PortMap map[string]PortInfo
type ServiceMap map[string]PortMap type ServiceMap map[string]PortMap
...@@ -64,7 +70,10 @@ func (e *Inspector) ExtractData(ctx context.Context) (*InspectData, error) { ...@@ -64,7 +70,10 @@ func (e *Inspector) ExtractData(ctx context.Context) (*InspectData, error) {
portMap := make(PortMap) portMap := make(PortMap)
for port, portSpec := range svcCtx.GetPublicPorts() { for port, portSpec := range svcCtx.GetPublicPorts() {
portMap[port] = int(portSpec.GetNumber()) portMap[port] = PortInfo{
Host: svcCtx.GetMaybePublicIPAddress(),
Port: int(portSpec.GetNumber()),
}
} }
if len(portMap) != 0 { if len(portMap) != 0 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment