Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
f9281ee
wait for login progress
frhuelsz Dec 15, 2025
8c1579b
Merge branch 'main' into user/frhuelsz/storm/e2e-2
frhuelsz Dec 16, 2025
75da58c
retrieve serial
frhuelsz Dec 16, 2025
b7e00dc
remove all ansi from log
frhuelsz Dec 16, 2025
59442d1
Improve artifact management
frhuelsz Dec 16, 2025
8fa9296
Merge branch 'main' into user/frhuelsz/storm/e2e-2
frhuelsz Dec 21, 2025
8b23b74
serial updates
frhuelsz Dec 22, 2025
6402941
serial console
frhuelsz Dec 22, 2025
107f0a0
Fully functional console monitoring
frhuelsz Dec 23, 2025
8b38db0
undo update
frhuelsz Dec 23, 2025
5475842
Update pipeline invocation with output
frhuelsz Dec 23, 2025
8e28210
More reliability
frhuelsz Dec 23, 2025
e5f1ac9
Use storm v0.4.0-alpha1
frhuelsz Dec 23, 2025
1e4be22
cleanup
frhuelsz Dec 23, 2025
01a900f
pr comments
frhuelsz Dec 23, 2025
775ac0a
pr comments
frhuelsz Dec 23, 2025
b61883c
Refactor to use runtime type everywhere
frhuelsz Dec 27, 2025
ed996fa
Check ssh
frhuelsz Dec 29, 2025
bad81cd
refactor netlisten
frhuelsz Dec 30, 2025
f8f27c4
Fix import order
frhuelsz Dec 30, 2025
99556b5
Update config construction
frhuelsz Dec 30, 2025
bd19b57
Cleanup change from other branch
frhuelsz Dec 30, 2025
c98ed7e
refactor netlisten
frhuelsz Dec 30, 2025
077d0a0
Merge branch 'user/frhuelsz/netlisten-refactor' into user/frhuelsz/st…
frhuelsz Dec 30, 2025
62608b2
Address copilot feedback
frhuelsz Dec 30, 2025
47022ed
more copilot comments
frhuelsz Dec 30, 2025
a81f67f
pr comments
frhuelsz Dec 30, 2025
54db467
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Dec 30, 2025
abfc0df
Updates
frhuelsz Dec 30, 2025
81cf17e
PR comments
frhuelsz Dec 30, 2025
ce5ff54
Comment
frhuelsz Dec 30, 2025
354b373
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Dec 30, 2025
abf6f3f
progress
frhuelsz Dec 30, 2025
9df0e11
Fix deadlock
frhuelsz Dec 30, 2025
af24a13
Close channel on error
frhuelsz Dec 30, 2025
5e4370c
avoid resource leak
frhuelsz Dec 30, 2025
4f9ea11
more robust console exit
frhuelsz Dec 31, 2025
cf1ae9c
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Dec 31, 2025
7f4ee67
AB-update
frhuelsz Dec 31, 2025
2d7996e
Better force exit console
frhuelsz Dec 31, 2025
3da66b7
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Dec 31, 2025
3b27bee
everything
frhuelsz Jan 1, 2026
742ba91
Merge branch 'main' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 2, 2026
a0a457d
pr comments
frhuelsz Jan 5, 2026
22c2ada
cleanup
frhuelsz Jan 5, 2026
5b5dc46
pr comments
frhuelsz Jan 5, 2026
66001c7
pr comments
frhuelsz Jan 5, 2026
305fc8c
pr comments
frhuelsz Jan 6, 2026
4202b1d
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 6, 2026
07fa9bb
pr comments
frhuelsz Jan 6, 2026
e878682
PR comments :)
frhuelsz Jan 6, 2026
3c7333e
pr comments
frhuelsz Jan 22, 2026
a34f55f
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 22, 2026
0ae93ac
pr comments
frhuelsz Jan 22, 2026
5c0dce3
pr comments
frhuelsz Jan 22, 2026
bbabee7
pr comments
frhuelsz Jan 22, 2026
dfa2506
pr comments
frhuelsz Jan 22, 2026
eb38c19
Merge branch 'user/frhuelsz/storm/e2e-2' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 22, 2026
f86cde9
pr comments
frhuelsz Jan 22, 2026
47ea524
pr comments
frhuelsz Jan 22, 2026
4aff2f9
Merge branch 'main' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 22, 2026
f290fb7
fix TridentRuntimeType
frhuelsz Jan 22, 2026
2633138
Merge branch 'main' into user/frhuelsz/storm/e2e-3
frhuelsz Jan 23, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ stages:
- name: ob_outputDirectory
value: $(Build.SourcesDirectory)/output
- name: ob_artifactBaseName
value: $(scenario)_$(System.JobAttempt)
value: $(SCENARIO)_$(System.JobAttempt)
- ${{ if eq(parameters.runtimeType, 'CONTAINER') }}:
- name: installerISOName
value: trident-container-installer
Expand Down Expand Up @@ -108,7 +108,7 @@ stages:

mkdir -p $(ob_outputDirectory)

./bin/storm-trident run "$(scenario)" \
./bin/storm-trident run "$(SCENARIO)" \
-o $(ob_outputDirectory) \
-- \
--pipeline-run \
Expand Down
5 changes: 5 additions & 0 deletions tools/pkg/hostconfig/getters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hostconfig

func (s *HostConfig) HasABUpdate() bool {
return s.Container.Exists("storage", "abUpdate")
Comment on lines +3 to +4
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The new HasABUpdate method only checks for the existence of "storage" and "abUpdate" keys but doesn't validate that abUpdate is not nil or has valid content. Consider adding validation to ensure the abUpdate configuration is actually usable.

Suggested change
func (s *HostConfig) HasABUpdate() bool {
return s.Container.Exists("storage", "abUpdate")
import (
"reflect"
"strings"
)
func (s *HostConfig) HasABUpdate() bool {
// First, confirm the key path exists as before.
if !s.Container.Exists("storage", "abUpdate") {
return false
}
// Use reflection to look for a Get("storage", "abUpdate") method on the container.
// This avoids relying on project-specific interfaces that may not exist.
containerValue := reflect.ValueOf(s.Container)
getMethod := containerValue.MethodByName("Get")
if !getMethod.IsValid() {
// No Get method available; fall back to the original Exists-based behavior.
return true
}
// Call Get("storage", "abUpdate") reflectively.
results := getMethod.Call([]reflect.Value{
reflect.ValueOf("storage"),
reflect.ValueOf("abUpdate"),
})
// If nothing was returned, treat it as unusable.
if len(results) == 0 {
return false
}
value := results[0].Interface()
if value == nil {
return false
}
// Apply simple sanity checks for common scalar types.
switch v := value.(type) {
case string:
return strings.TrimSpace(v) != ""
case []byte:
return len(v) > 0
default:
// For other non-nil types, assume the configuration is usable.
return true
}

Copilot uses AI. Check for mistakes.
}
79 changes: 79 additions & 0 deletions tools/pkg/hostconfig/hostconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package hostconfig

import (
"fmt"

"github.com/Jeffail/gabs/v2"
"gopkg.in/yaml.v3"
)

// HostConfig represents the configuration for a host. It uses a gabs.Container
// to manage the underlying data structure.
type HostConfig struct {
*gabs.Container
}

// NewHostConfigFromContainer creates a new HostConfig from an existing gabs.Container.
func NewHostConfigFromContainer(container *gabs.Container) HostConfig {
return HostConfig{
Container: container,
}
}

// NewHostConfigFromInterface creates a new HostConfig from a generic interface{}.
func NewHostConfigFromInterface(data interface{}) HostConfig {
return HostConfig{
Container: gabs.Wrap(data),
}
}

// NewHostConfigFromYaml creates a new HostConfig from a YAML byte slice.
func NewHostConfigFromYaml(yamlData []byte) (HostConfig, error) {
var data map[string]any
err := yaml.Unmarshal(yamlData, &data)
if err != nil {
return HostConfig{}, fmt.Errorf("failed to unmarshal YAML data: %w", err)
}

return HostConfig{
Container: gabs.Wrap(data),
}, nil
}

// GetContainer returns the underlying gabs.Container of the HostConfig.
func (hc *HostConfig) GetContainer() *gabs.Container {
return hc.Container
}

// Data returns the underlying data of the HostConfig as an interface{}.
func (hc *HostConfig) ToInterface() interface{} {
return hc.Container.Data()
}

// ToYaml serializes the HostConfig to a YAML byte slice.
func (hc *HostConfig) ToYaml() ([]byte, error) {
data, err := yaml.Marshal(hc.Data())
if err != nil {
return nil, fmt.Errorf("failed to marshal HostConfig to YAML: %w", err)
}

return data, nil
}

// Clone creates a deep copy of the HostConfig.
func (s *HostConfig) Clone() (HostConfig, error) {
// Unfortunately there is no direct way to clone an interface{}/any type, in
// Go, so the easiest way is to serialize to YAML and deserialize back. This
// is by no means the most efficient way, but it works for our purposes.
yamlData, err := s.ToYaml()
if err != nil {
return HostConfig{}, err
}

copy, err := NewHostConfigFromYaml(yamlData)
if err != nil {
return HostConfig{}, err
}

return copy, nil
}
15 changes: 8 additions & 7 deletions tools/pkg/netlaunch/netlaunch.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ func RunNetlaunch(ctx context.Context, config *NetLaunchConfig) error {
enable_phonehome_listening := config.ListenPort != 0

result := make(chan phonehome.PhoneHomeResult)
server := &http.Server{}
mux := http.NewServeMux()
server := &http.Server{Handler: mux}

// Create the final address that will be announced to the BMC and Trident.
var announceIp string
Expand Down Expand Up @@ -137,7 +138,7 @@ func RunNetlaunch(ctx context.Context, config *NetLaunchConfig) error {
}
}

http.HandleFunc("/provision.iso", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/provision.iso", func(w http.ResponseWriter, r *http.Request) {
isoLogFunc(r.RemoteAddr)
http.ServeContent(w, r, "provision.iso", time.Now(), bytes.NewReader(iso))
})
Expand All @@ -146,7 +147,7 @@ func RunNetlaunch(ctx context.Context, config *NetLaunchConfig) error {
enable_phonehome_listening = true
} else {
// Otherwise, serve the iso as-is
http.HandleFunc("/provision.iso", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/provision.iso", func(w http.ResponseWriter, r *http.Request) {
isoLogFunc(r.RemoteAddr)
http.ServeContent(w, r, "provision.iso", time.Now(), bytes.NewReader(iso))
terminateFunc()
Expand All @@ -156,17 +157,17 @@ func RunNetlaunch(ctx context.Context, config *NetLaunchConfig) error {
// If we're expecting Trident to reach back, we need to listen for it.
if enable_phonehome_listening {
// Set up listening for phonehome
phonehome.SetupPhoneHomeServer(result, config.RemoteAddressFile)
phonehome.SetupPhoneHomeServer(mux, result, config.RemoteAddressFile)

// Set up listening for logstream
logstreamFull, err := phonehome.SetupLogstream(config.LogstreamFile)
logstreamFull, err := phonehome.SetupLogstream(mux, config.LogstreamFile)
if err != nil {
return fmt.Errorf("failed to setup logstream: %w", err)
}
defer logstreamFull.Close()

// Set up listening for tracestream
traceFile, err := phonehome.SetupTraceStream(config.TracestreamFile)
traceFile, err := phonehome.SetupTraceStream(mux, config.TracestreamFile)
if err != nil {
return fmt.Errorf("failed to setup tracestream: %w", err)
}
Expand All @@ -175,7 +176,7 @@ func RunNetlaunch(ctx context.Context, config *NetLaunchConfig) error {
}

if len(config.ServeDirectory) != 0 {
http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(config.ServeDirectory))))
mux.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(config.ServeDirectory))))
}

// Start the HTTP server
Expand Down
11 changes: 6 additions & 5 deletions tools/pkg/netlisten/netlisten.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,27 @@ func RunNetlisten(ctx context.Context, config *netlaunch.NetListenConfig) error

// Set up listening
result := make(chan phonehome.PhoneHomeResult)
server := &http.Server{}
mux := http.NewServeMux()
server := &http.Server{Handler: mux}

// Set up listening for phonehome
phonehome.SetupPhoneHomeServer(result, "")
phonehome.SetupPhoneHomeServer(mux, result, "")
// Set up listening for logstream
logstreamFull, err := phonehome.SetupLogstream(config.LogstreamFile)
logstreamFull, err := phonehome.SetupLogstream(mux, config.LogstreamFile)
if err != nil {
return fmt.Errorf("failed to set up logstream: %w", err)
}
defer logstreamFull.Close()

// Set up listening for tracestream
traceFile, err := phonehome.SetupTraceStream(config.TracestreamFile)
traceFile, err := phonehome.SetupTraceStream(mux, config.TracestreamFile)
if err != nil {
return fmt.Errorf("failed to set up trace stream: %w", err)
}
defer traceFile.Close()

if len(config.ServeDirectory) != 0 {
http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(config.ServeDirectory))))
mux.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(config.ServeDirectory))))
}

// If serial over SSH is configured, listen for serial output.
Expand Down
7 changes: 5 additions & 2 deletions tools/pkg/phonehome/logstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ func (l LogLevel) AsLogrusLevel() log.Level {
}
}

func SetupLogstream(backgroundLogFile string) (*os.File, error) {
func SetupLogstream(mux *http.ServeMux, backgroundLogFile string) (*os.File, error) {
colorize := color.New(color.FgYellow).SprintfFunc()
if mux == nil {
mux = http.DefaultServeMux
}

// Setup background logger
bgLogger := log.New()
Expand All @@ -63,7 +66,7 @@ func SetupLogstream(backgroundLogFile string) (*os.File, error) {
ForceColors: true,
})

http.HandleFunc("/logstream", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/logstream", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201)
w.Write([]byte("OK"))

Expand Down
8 changes: 6 additions & 2 deletions tools/pkg/phonehome/phonehome.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ const (
PhoneHomeResultError PhoneHomeResultState = "error"
)

func SetupPhoneHomeServer(result chan<- PhoneHomeResult, remoteAddressFile string) {
http.HandleFunc("/phonehome", func(w http.ResponseWriter, r *http.Request) {
func SetupPhoneHomeServer(mux *http.ServeMux, result chan<- PhoneHomeResult, remoteAddressFile string) {
if mux == nil {
mux = http.DefaultServeMux
}

mux.HandleFunc("/phonehome", func(w http.ResponseWriter, r *http.Request) {
// log.WithField("remote-address", r.RemoteAddr).Info("Phone Home")
w.WriteHeader(201)
w.Write([]byte("OK"))
Expand Down
8 changes: 6 additions & 2 deletions tools/pkg/phonehome/tracestream.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ type TraceEntry struct {
PlatformInfo map[string]interface{} `json:"platform_info"`
}

func SetupTraceStream(filepath string) (*os.File, error) {
func SetupTraceStream(mux *http.ServeMux, filepath string) (*os.File, error) {
if filepath == "" {
return nil, nil
}

if mux == nil {
mux = http.DefaultServeMux
}

// Setup a file to store the trace data
traceFile, err := os.Create(filepath)
if err != nil {
Expand All @@ -32,7 +36,7 @@ func SetupTraceStream(filepath string) (*os.File, error) {
// Generate a UUID to group events coming in from the same Trident run
traceID := uuid.New().String()

http.HandleFunc("/tracestream", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/tracestream", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201)
w.Write([]byte("OK"))

Expand Down
4 changes: 2 additions & 2 deletions tools/pkg/virtdeploy/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,14 +573,14 @@ func (rc *virtDeployResourceConfig) setupVm(vm *VirtDeployVM) error {
// libvirt sees it. We use the DomainGetXMLDesc method with 0 flags to get
// the full XML. The previous domainXML variable may not include all
// auto-generated fields.
rXml, err := rc.lv.DomainGetXMLDesc(vm.domain, 0)
finalDomainXml, err := rc.lv.DomainGetXMLDesc(vm.domain, 0)
if err != nil {
return fmt.Errorf("get domain XML description: %w", err)
}

// Initialize empty struct and unmarshal the XML into it
vm.domainDefinition = &libvirtxml.Domain{}
err = vm.domainDefinition.Unmarshal(rXml)
err = vm.domainDefinition.Unmarshal(finalDomainXml)
if err != nil {
return fmt.Errorf("unmarshal domain XML description: %w", err)
}
Expand Down
26 changes: 16 additions & 10 deletions tools/storm/e2e/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package e2e
import (
"embed"
"fmt"
"tridenttools/pkg/hostconfig"
"tridenttools/storm/e2e/scenario"
"tridenttools/storm/e2e/testrings"
"tridenttools/storm/utils/trident"

"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -46,8 +48,7 @@ func DiscoverTridentScenarios(log *logrus.Logger) ([]scenario.TridentE2EScenario
log.Fatalf("Failed to read configuration file: %v", err)
}

var hostConfig map[string]any
err = yaml.Unmarshal(configYaml, &hostConfig)
hostConfig, err := hostconfig.NewHostConfigFromYaml(configYaml)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal configuration file for '%s': %v", name, err)
}
Expand All @@ -70,7 +71,7 @@ func getConfigPath(scenarioName string) string {
}

// Produces all scenarios from a given configuration.
func produceScenariosFromConfig(name string, conf scenarioConfig, hostConfig map[string]interface{}) ([]scenario.TridentE2EScenario, error) {
func produceScenariosFromConfig(name string, conf scenarioConfig, hostConfig hostconfig.HostConfig) ([]scenario.TridentE2EScenario, error) {
var scenarios []scenario.TridentE2EScenario

// Iterate over all hardware types
Expand All @@ -85,13 +86,13 @@ func produceScenariosFromConfig(name string, conf scenarioConfig, hostConfig map
}

// Iterate over all runtime types
for _, rt := range scenario.RuntimeTypes() {
for _, rt := range trident.RuntimeTypes() {
// For the current runtime type, get the corresponding test ring from the runtime configuration
var ring testrings.TestRing
switch rt {
case scenario.RuntimeTypeHost:
case trident.RuntimeTypeHost:
ring = currentRtConfig.Host
case scenario.RuntimeTypeContainer:
case trident.RuntimeTypeContainer:
ring = currentRtConfig.Container
}

Expand Down Expand Up @@ -121,10 +122,10 @@ func produceScenariosFromConfig(name string, conf scenarioConfig, hostConfig map
// in any ring.
func produceScenario(
name string,
config map[string]interface{},
config hostconfig.HostConfig,
configParams scenario.TridentE2EHostConfigParams,
hardware scenario.HardwareType,
runtime scenario.RuntimeType,
runtime trident.RuntimeType,
lowestRing testrings.TestRing,
) (*scenario.TridentE2EScenario, error) {
// Get the list of all target rings for this scenario. This is the list of
Expand All @@ -151,15 +152,20 @@ func produceScenario(
tags = append(tags, string(ring))
}

return scenario.NewTridentE2EScenario(
newScenario, err := scenario.NewTridentE2EScenario(
fmt.Sprintf("%s_%s-%s", name, hardware, runtime),
tags,
config,
configParams,
hardware,
runtime,
rings,
), nil
)
if err != nil {
return nil, err
}

return newScenario, nil
}

// Top level types for unmarshaling the configurations.yaml file.
Expand Down
Loading
Loading