Skip to content

Commit 0df1d39

Browse files
committed
Add BMC emulator container management support to vbmctl
Add BMCEmulatorConfig API type and container lifecycle management for BMC emulator containers (vbmc and sushy-tools). - Add BMCEmulatorConfig API fields - Add defaults and validation in vbmctl config - Implement container creation helper with env conversion and volume mounts - Update package docs to include BMC emulator usage Signed-off-by: Tero Kauppinen <tero.kauppinen@est.tech>
1 parent a3b6f4c commit 0df1d39

9 files changed

Lines changed: 494 additions & 14 deletions

File tree

hack/ci-e2e.sh

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ IP_ADDRESS="192.168.222.1"
114114

115115
if [[ "${BMO_E2E_EMULATOR}" == "vbmc" ]]; then
116116
# Start VBMC
117-
docker start vbmc || docker run --name vbmc --network host -d \
118-
-v /var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock \
119-
-v /var/run/libvirt/libvirt-sock-ro:/var/run/libvirt/libvirt-sock-ro \
120-
quay.io/metal3-io/vbmc
117+
./bin/vbmctl create bmc-emulator --emulator-type "vbmc" --image "quay.io/metal3-io/vbmc"
121118

122119
readarray -t BMCS < <(yq e -o=j -I=0 '.[]' "${E2E_BMCS_CONF_FILE}")
123120
for bmc in "${BMCS[@]}"; do
@@ -131,12 +128,7 @@ elif [[ "${BMO_E2E_EMULATOR}" == "sushy-tools" ]]; then
131128
# Sushy-tools variables
132129
SUSHY_EMULATOR_FILE="${REPO_ROOT}"/test/e2e/sushy-tools/sushy-emulator.conf
133130
# Start sushy-tools
134-
docker start sushy-tools || docker run --name sushy-tools -d --network host \
135-
-v "${SUSHY_EMULATOR_FILE}":/etc/sushy/sushy-emulator.conf:Z \
136-
-v /var/run/libvirt:/var/run/libvirt:Z \
137-
-e SUSHY_EMULATOR_CONFIG=/etc/sushy/sushy-emulator.conf \
138-
quay.io/metal3-io/sushy-tools:latest sushy-emulator
139-
131+
./bin/vbmctl create bmc-emulator --emulator-type "sushy-tools" --image "quay.io/metal3-io/sushy-tools:latest" --config-file "${SUSHY_EMULATOR_FILE}"
140132
else
141133
echo "FATAL: Invalid e2e emulator specified: ${BMO_E2E_EMULATOR}"
142134
exit 1

hack/clean-e2e.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
44
cd "${REPO_ROOT}" || exit 1
55

6-
docker rm -f vbmc
6+
docker rm -f vbmctl-vbmc
77
docker rm -f vbmctl-image-server-e2e
8-
docker rm -f sushy-tools
8+
docker rm -f vbmctl-sushy-tools
99

1010
"${REPO_ROOT}/tools/bmh_test/clean_local_bmh_test_setup.sh" "^bmo-e2e-"
1111

test/vbmctl/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ This tool is under active development.
1818
| `vbmctl status` | ✅ Implemented (basic) |
1919
| Configurable volumes | ✅ Implemented |
2020
| Network management | ⚠️ Partially implemented (only libvirt networks) |
21-
| BMC emulator support | ❌ TODO |
21+
| BMC emulator support | ✅ Implemented (basic) |
2222
| Image server | ✅ Implemented (basic) |
2323
| State management (persistent state) | ❌ TODO |
2424

@@ -31,6 +31,7 @@ This tool is under active development.
3131
- **Library Support**: Can be imported as a Go module for programmatic use
3232
- **Libvirt network management**: create and delete libvirt networks
3333
- **Image Server Management**: Create, delete, list image server
34+
- **BMC Emulator Management**: Create, delete, list server
3435

3536
## Build Tags
3637

@@ -82,6 +83,9 @@ vbmctl create bml
8283
# a name is specified, vbmctl will automatically add the prefix `vbmctl-`.
8384
vbmctl create image-server
8485

86+
# Create a BMC server with default settings.
87+
vbmctl create bmc-emulator
88+
8589
# Check status
8690
vbmctl status
8791

@@ -97,6 +101,9 @@ vbmctl delete network
97101
# Delete the image server
98102
vbmctl delete image-server
99103

104+
# Delete the bmc emulator instance
105+
vbmctl delete bmc-emulator
106+
100107
# Show help
101108
vbmctl --help
102109
```
@@ -168,6 +175,10 @@ spec:
168175
imageServer:
169176
dataDir: "/tmp"
170177
port: 80
178+
bmcEmulator:
179+
type: "sushy-tools"
180+
configFile: "vbmc-emulator-file"
181+
image: "bmc-emulator:latest"`,
171182
```
172183

173184
The `spec.vms` section defines the VMs that will be created when you run `vbmctl
@@ -258,6 +269,16 @@ func main() {
258269
log.Fatal(err)
259270
}
260271
272+
// Create a BMC emulator
273+
err = containers.CreateBMCEmulatorInstance(ctx, &api.BMCEmulatorConfig{
274+
Image: "my-bmc-emulator:latest",
275+
Type: "sushy-tools",
276+
ConfigFile: "/path/to/config/file",
277+
})
278+
if err != nil {
279+
log.Fatal(err)
280+
}
281+
261282
fmt.Printf("Created VM: %s (UUID: %s)\n", vm.Config.Name, vm.UUID)
262283
}
263284
```

test/vbmctl/cmd/vbmctl/main.go

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func newCreateCmd() *cobra.Command {
8888
cmd.AddCommand(newCreateBMLCmd())
8989
cmd.AddCommand(newCreateNetworkCmd())
9090
cmd.AddCommand(newCreateImageServerCmd())
91+
cmd.AddCommand(newCreateBMCEmulatorCmd())
9192
return cmd
9293
}
9394

@@ -210,7 +211,11 @@ Example configuration:
210211
containerPort: 8080
211212
dataDir: "/var/lib/vbmctl/images"
212213
containerDataDir: "/usr/share/nginx/html",
213-
containerName: "vbmctl-image-server"`,
214+
containerName: "vbmctl-image-server"
215+
bmcEmulator:
216+
type: "sushy-tools"
217+
configFile: "vbmc-emulator-file"
218+
image: "bmc-emulator:latest"`,
214219
RunE: func(_ *cobra.Command, _ []string) error {
215220
ctx, cancel := contextWithSignal()
216221
defer cancel()
@@ -234,6 +239,16 @@ Example configuration:
234239
fmt.Println("No image server configuration found in the config file.")
235240
}
236241

242+
if cfg.Spec.BMCEmulator != nil {
243+
err = containers.CreateBMCEmulatorInstance(ctx, cfg.Spec.BMCEmulator)
244+
if err != nil {
245+
return err
246+
}
247+
} else {
248+
//nolint:forbidigo // CLI output is intentional
249+
fmt.Println("No BMC emulator configuration found in the config file.")
250+
}
251+
237252
conn, err := libvirtgo.NewConnect(cfg.Spec.Libvirt.URI)
238253
if err != nil {
239254
return fmt.Errorf("failed to connect to libvirt: %w", err)
@@ -420,6 +435,62 @@ func newCreateImageServerCmd() *cobra.Command {
420435
return cmd
421436
}
422437

438+
func newCreateBMCEmulatorCmd() *cobra.Command {
439+
var (
440+
emulatorType string
441+
image string
442+
configFile string
443+
)
444+
445+
cmd := &cobra.Command{
446+
Use: "bmc-emulator",
447+
Short: "Create a BMC emulator instance",
448+
Long: "Create a BMC emulator instance to be used for testing.",
449+
RunE: func(_ *cobra.Command, _ []string) error {
450+
ctx, cancel := contextWithSignal()
451+
defer cancel()
452+
453+
cfg, err := loadConfig()
454+
if err != nil {
455+
return err
456+
}
457+
458+
// Resolve effective BMC emulator config: config file values, falling back to defaults.
459+
// Command-line flags take precedence over both.
460+
effective := &vbmctlapi.BMCEmulatorConfig{
461+
Type: config.DefaultBMCEmulatorType,
462+
Image: config.DefaultBMCEmulatorImage,
463+
ConfigFile: config.DefaultBMCEmulatorConfigFile,
464+
}
465+
if cfg.Spec.BMCEmulator != nil {
466+
effective = cfg.Spec.BMCEmulator
467+
}
468+
469+
if emulatorType == "" {
470+
emulatorType = effective.Type
471+
}
472+
if image == "" {
473+
image = effective.Image
474+
}
475+
if configFile == "" {
476+
configFile = effective.ConfigFile
477+
}
478+
479+
return containers.CreateBMCEmulatorInstance(ctx, &vbmctlapi.BMCEmulatorConfig{
480+
Type: emulatorType,
481+
Image: image,
482+
ConfigFile: configFile,
483+
})
484+
},
485+
}
486+
487+
cmd.Flags().StringVar(&emulatorType, "emulator-type", "", "type of the BMC emulator (vbmc or sushy-tools, default is "+config.DefaultBMCEmulatorType+" if not set in config file)")
488+
cmd.Flags().StringVar(&image, "image", "", "container image to use for the BMC emulator (default is "+config.DefaultBMCEmulatorImage+" if not set in config file)")
489+
cmd.Flags().StringVar(&configFile, "config-file", "", "configuration file to use for the BMC emulator in case of sushy-tools (default is "+config.DefaultBMCEmulatorConfigFile+" if not set in config file)")
490+
491+
return cmd
492+
}
493+
423494
func newDeleteCmd() *cobra.Command {
424495
cmd := &cobra.Command{
425496
Use: "delete",
@@ -431,6 +502,7 @@ func newDeleteCmd() *cobra.Command {
431502
cmd.AddCommand(newDeleteBMLCmd())
432503
cmd.AddCommand(newDeleteNetworkCmd())
433504
cmd.AddCommand(newDeleteImageServerCmd())
505+
cmd.AddCommand(newDeleteBMCEmulatorCmd())
434506
return cmd
435507
}
436508

@@ -565,6 +637,18 @@ func newDeleteBMLCmd() *cobra.Command {
565637
fmt.Println("No image server configuration found in the config file.")
566638
}
567639

640+
if cfg.Spec.BMCEmulator != nil {
641+
err := containers.DeleteBMCEmulatorInstance(ctx, cfg.Spec.BMCEmulator.Type)
642+
// don't fail the whole command if BMC emulator deletion fails, just log the error
643+
if err != nil {
644+
//nolint:forbidigo // CLI output is intentional
645+
fmt.Printf("%v\n", err)
646+
}
647+
} else {
648+
//nolint:forbidigo // CLI output is intentional
649+
fmt.Println("No BMC emulator configuration found in the config file.")
650+
}
651+
568652
return nil
569653
},
570654
}
@@ -646,8 +730,43 @@ func newDeleteImageServerCmd() *cobra.Command {
646730
return cmd
647731
}
648732

733+
func newDeleteBMCEmulatorCmd() *cobra.Command {
734+
var emulatorType string
735+
736+
cmd := &cobra.Command{
737+
Use: "bmc-emulator",
738+
Short: "Delete the BMC emulator instance",
739+
Long: "Delete the BMC emulator instance used for provisioning.",
740+
RunE: func(_ *cobra.Command, _ []string) error {
741+
ctx, cancel := contextWithSignal()
742+
defer cancel()
743+
744+
cfg, err := loadConfig()
745+
if err != nil {
746+
return err
747+
}
748+
749+
// command-line flag takes precedence over config file value
750+
if emulatorType == "" {
751+
if cfg.Spec.BMCEmulator != nil {
752+
emulatorType = cfg.Spec.BMCEmulator.Type
753+
} else {
754+
emulatorType = config.DefaultBMCEmulatorType
755+
}
756+
}
757+
758+
return containers.DeleteBMCEmulatorInstance(ctx, emulatorType)
759+
},
760+
}
761+
762+
cmd.Flags().StringVar(&emulatorType, "emulator-type", "", "type of the BMC emulator container to delete (default is "+config.DefaultBMCEmulatorType+" if not set in config file)")
763+
764+
return cmd
765+
}
766+
649767
func newStatusCmd() *cobra.Command {
650768
var containerName string
769+
var emulatorType string
651770

652771
cmd := &cobra.Command{
653772
Use: "status",
@@ -695,9 +814,25 @@ func newStatusCmd() *cobra.Command {
695814
return err
696815
}
697816

817+
// command-line flag takes precedence over config file value
818+
if emulatorType == "" {
819+
if cfg.Spec.BMCEmulator != nil {
820+
emulatorType = cfg.Spec.BMCEmulator.Type
821+
} else {
822+
emulatorType = config.DefaultBMCEmulatorType
823+
}
824+
}
825+
// check if the bmc emulator is present
826+
emulatorInfo, err := containers.GetBMCEmulatorInfo(ctx, emulatorType)
827+
if err != nil {
828+
return err
829+
}
830+
698831
//nolint:forbidigo // CLI output is intentional
699832
fmt.Printf("Image Server container: %s\n", containerInfo)
700833
//nolint:forbidigo // CLI output is intentional
834+
fmt.Printf("BMC Emulator container: %s\n", emulatorInfo)
835+
//nolint:forbidigo // CLI output is intentional
701836
fmt.Println("Virtual Machines:")
702837
//nolint:forbidigo // CLI output is intentional
703838
fmt.Println(" NAME\t\tSTATE\t\tMEMORY\tVCPUs")
@@ -712,6 +847,7 @@ func newStatusCmd() *cobra.Command {
712847
}
713848

714849
cmd.Flags().StringVar(&containerName, "name", "", "name of the image server container (default is "+config.DefaultImageServerContainerName+" if not set in config file)")
850+
cmd.Flags().StringVar(&emulatorType, "emulator-type", "", "type of the BMC emulator container to check for (default is "+config.DefaultBMCEmulatorType+" if not set in config file)")
715851

716852
return cmd
717853
}
@@ -793,6 +929,14 @@ func newConfigViewCmd() *cobra.Command {
793929
cfg.Spec.ImageServer.ContainerName)
794930
}
795931

932+
if cfg.Spec.BMCEmulator != nil {
933+
//nolint:forbidigo // CLI output is intentional
934+
fmt.Printf("BMC Emulator:\n Emulator Type: %s\n Config File: %s\n Image: %s\n",
935+
cfg.Spec.BMCEmulator.Type,
936+
cfg.Spec.BMCEmulator.ConfigFile,
937+
cfg.Spec.BMCEmulator.Image)
938+
}
939+
796940
return nil
797941
},
798942
}

test/vbmctl/pkg/api/types.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,36 @@ type ImageServerConfig struct {
5050
ContainerName string `json:"containerName" yaml:"containerName"`
5151
}
5252

53+
// BMCEmulatorConfig represents the configuration for the BMC emulator.
54+
type BMCEmulatorConfig struct {
55+
// BMC Emulator type
56+
Type string `json:"type" yaml:"type"`
57+
58+
// BMC Emulator file
59+
ConfigFile string `json:"configFile" yaml:"configFile"`
60+
61+
// Image is the container image to use for the BMC emulator.
62+
Image string `json:"image" yaml:"image"`
63+
64+
// Env is a map of environment variables to set in the BMC emulator container.
65+
Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"`
66+
67+
// ContainerName is the name of the container to create for the BMC emulator.
68+
ContainerName string `json:"containerName" yaml:"containerName"`
69+
70+
// Volume1HostPath is the host path for the first volume to mount.
71+
Volume1HostPath string `json:"volume1HostPath" yaml:"volume1HostPath"`
72+
73+
// Volume1ContainerPath is the container path for the first volume to mount.
74+
Volume1ContainerPath string `json:"volume1ContainerPath" yaml:"volume1ContainerPath"`
75+
76+
// Volume2HostPath is the host path for the second volume to mount.
77+
Volume2HostPath string `json:"volume2HostPath" yaml:"volume2HostPath"`
78+
79+
// Volume2ContainerPath is the container path for the second volume to mount.
80+
Volume2ContainerPath string `json:"volume2ContainerPath" yaml:"volume2ContainerPath"`
81+
}
82+
5383
// NetworkAttachment represents a network interface attached to a VM.
5484
type NetworkAttachment struct {
5585
// Network is the name of the libvirt network to attach to.

0 commit comments

Comments
 (0)