feat: block device mappings

This commit is contained in:
Julian Sommer
2026-01-29 14:10:04 +10:00
parent 20de0a7989
commit 3bca0e69df
6 changed files with 124 additions and 20 deletions
+5
View File
@@ -24,6 +24,11 @@
- Confirm cloud-init will resize volume on boot and configured correctly.
- Attach additional volumes
- Attach to VPC / Security group (required Open vSwitch implementation)
- only allow hive to use x% of cpu and memory. dont allow allocating 100% of resources.
- placement group instances, allow spreading instance group between nodes.
- capabilities flag
- more instance types - per cpu gen
- multi part uploads on frontend
## EC2 describe-instances
+14 -1
View File
@@ -1420,7 +1420,7 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
var successCount int
for _, instance := range instances {
// Prepare the root volume, cloud-init, EFI drives via NBD (AMI clone to new volume)
err = d.instanceService.GenerateVolumes(runInstancesInput, instance)
volumeInfos, err := d.instanceService.GenerateVolumes(runInstancesInput, instance)
if err != nil {
slog.Error("handleEC2RunInstances GenerateVolumes failed", "instanceId", instance.ID, "err", err)
d.resourceMgr.deallocate(instanceType)
@@ -1428,6 +1428,19 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) {
continue
}
// Populate BlockDeviceMappings on the ec2.Instance
instance.Instance.BlockDeviceMappings = make([]*ec2.InstanceBlockDeviceMapping, 0, len(volumeInfos))
for _, vi := range volumeInfos {
mapping := &ec2.InstanceBlockDeviceMapping{}
mapping.SetDeviceName(vi.DeviceName)
mapping.Ebs = &ec2.EbsInstanceBlockDevice{}
mapping.Ebs.SetVolumeId(vi.VolumeId)
mapping.Ebs.SetAttachTime(vi.AttachTime)
mapping.Ebs.SetDeleteOnTermination(vi.DeleteOnTermination)
mapping.Ebs.SetStatus("attached")
instance.Instance.BlockDeviceMappings = append(instance.Instance.BlockDeviceMappings, mapping)
}
// Launch the instance infrastructure (QEMU, QMP, NATS subscriptions)
err = d.LaunchInstance(instance)
if err != nil {
+50 -13
View File
@@ -75,6 +75,15 @@ type CloudInitMetaData struct {
Hostname string
}
// VolumeInfo holds volume information returned from GenerateVolumes
// for populating BlockDeviceMappings in the EC2 API response
type VolumeInfo struct {
VolumeId string
DeviceName string
AttachTime time.Time
DeleteOnTermination bool
}
// InstanceServiceImpl handles daemon-side EC2 instance operations
type InstanceServiceImpl struct {
config *config.Config
@@ -132,28 +141,43 @@ func (s *InstanceServiceImpl) RunInstance(input *ec2.RunInstancesInput) (*vm.VM,
return instance, ec2Instance, nil
}
func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, instance *vm.VM) (err error) {
func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, instance *vm.VM) ([]VolumeInfo, error) {
var size int = 4 * 1024 * 1024 * 1024 // 4GB default size
var deviceName string
var deviceName string = "/dev/xvda" // Default device name
var volumeType string
var iops int
var imageId string
var snapshotId string
var deleteOnTermination bool = true // Default to true
// Handle block device mappings
if len(input.BlockDeviceMappings) > 0 {
size = int(*input.BlockDeviceMappings[0].Ebs.VolumeSize)
deviceName = *input.BlockDeviceMappings[0].DeviceName
volumeType = *input.BlockDeviceMappings[0].Ebs.VolumeType
iops = int(*input.BlockDeviceMappings[0].Ebs.Iops)
bdm := input.BlockDeviceMappings[0]
if bdm.DeviceName != nil {
deviceName = *bdm.DeviceName
}
if bdm.Ebs != nil {
if bdm.Ebs.VolumeSize != nil {
size = int(*bdm.Ebs.VolumeSize)
}
if bdm.Ebs.VolumeType != nil {
volumeType = *bdm.Ebs.VolumeType
}
if bdm.Ebs.Iops != nil {
iops = int(*bdm.Ebs.Iops)
}
if bdm.Ebs.DeleteOnTermination != nil {
deleteOnTermination = *bdm.Ebs.DeleteOnTermination
}
}
}
// Determine image ID and snapshot ID
if strings.HasPrefix(*input.ImageId, "ami-") {
randomNumber, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
if err != nil {
return err
return nil, err
}
imageId = viperblock.GenerateVolumeID("vol", fmt.Sprintf("%d-%s", randomNumber, *input.ImageId), "predastore", time.Now().Unix())
snapshotId = *input.ImageId
@@ -161,11 +185,14 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
imageId = *input.ImageId
}
// Capture attach time for the root volume
attachTime := time.Now()
volumeConfig := viperblock.VolumeConfig{
VolumeMetadata: viperblock.VolumeMetadata{
VolumeID: imageId,
SizeGiB: utils.SafeIntToUint64(size / 1024 / 1024 / 1024),
CreatedAt: time.Now(),
CreatedAt: attachTime,
DeviceName: deviceName,
VolumeType: volumeType,
IOPS: iops,
@@ -174,26 +201,36 @@ func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, inst
}
// Step 1: Create or validate root volume
err = s.prepareRootVolume(input, imageId, size, volumeConfig, instance)
err := s.prepareRootVolume(input, imageId, size, volumeConfig, instance)
if err != nil {
return err
return nil, err
}
// Step 2: Create EFI partition
err = s.prepareEFIVolume(imageId, volumeConfig, instance)
if err != nil {
return err
return nil, err
}
// Step 3: Create cloud-init volume if needed
if input.KeyName != nil && *input.KeyName != "" || (input.UserData != nil && *input.UserData != "") {
err = s.prepareCloudInitVolume(input, imageId, volumeConfig, instance)
if err != nil {
return err
return nil, err
}
}
return nil
// Return volume info for the root volume only (EFI and cloud-init are internal)
volumeInfos := []VolumeInfo{
{
VolumeId: imageId,
DeviceName: deviceName,
AttachTime: attachTime,
DeleteOnTermination: deleteOnTermination,
},
}
return volumeInfos, nil
}
@@ -114,6 +114,42 @@ function InstanceDetail() {
</DetailCard.Content>
</DetailCard> */}
{/* Block Device Mappings */}
{instance.BlockDeviceMappings &&
instance.BlockDeviceMappings.length > 0 && (
<DetailCard>
<DetailCard.Header>Block Device Mappings</DetailCard.Header>
<DetailCard.Content>
<DetailRow
label="Device Name"
value={instance.BlockDeviceMappings[0]?.DeviceName}
/>
<DetailRow
label="Volume ID"
value={instance.BlockDeviceMappings[0]?.Ebs?.VolumeId}
/>
<DetailRow
label="Status"
value={instance.BlockDeviceMappings[0]?.Ebs?.Status}
/>
<DetailRow
label="Attach Time"
value={formatDateTime(
instance.BlockDeviceMappings[0]?.Ebs?.AttachTime,
)}
/>
<DetailRow
label="Delete on Termination"
value={
instance.BlockDeviceMappings[0]?.Ebs?.DeleteOnTermination
? "Yes"
: "No"
}
/>
</DetailCard.Content>
</DetailCard>
)}
{/* AMI Details */}
{image && <AmiDetails image={image} />}
</div>
+2
View File
@@ -16,6 +16,8 @@ rm -rf ~/hive/viperblock/*
rm -rf ~/hive/nats/*
rm -rf ~/hive/images/*
# Enable pprof for development
PPROF_ENABLED=1 PPROF_OUTPUT=/tmp/hive-vm.prof ./scripts/start-dev.sh
+17 -6
View File
@@ -4,6 +4,10 @@
# This script starts all required services in the correct order using Hive service commands
# Usage: ./scripts/start-dev.sh [data-dir]
# data-dir: Optional data directory path (default: ~/hive)
#
# Environment variables:
# UI=false Skip starting Hive UI (e.g., UI=false ./scripts/start-dev.sh)
# HIVE_SKIP_BUILD=true Skip building binaries before starting
set -e
@@ -270,18 +274,25 @@ start_service "awsgw" "$AWSGW_CMD"
#check_service "awsgw" "9999"
# 6️⃣ Start Hive UI
echo ""
echo "6️⃣. Starting Hive UI..."
# 6️⃣ Start Hive UI (skip with UI=false)
if [ "${UI}" != "false" ]; then
echo ""
echo "6️⃣. Starting Hive UI..."
HIVEUI_CMD="./bin/hive service hive-ui start"
HIVEUI_CMD="./bin/hive service hive-ui start"
start_service "hive-ui" "$HIVEUI_CMD"
start_service "hive-ui" "$HIVEUI_CMD"
else
echo ""
echo "6️⃣. Skipping Hive UI (UI=false)"
fi
echo ""
echo "🔗 Service endpoints will be:"
echo " - Hive UI: https://localhost:3000"
if [ "${UI}" != "false" ]; then
echo " - Hive UI: https://localhost:3000"
fi
echo " - NATS: nats://localhost:4222"
echo " - Predastore: https://localhost:8443"
echo " - AWS Gateway: https://localhost:9999"