diff --git a/TODO.md b/TODO.md index 3aab8730b..1e2f20092 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/hive/daemon/daemon.go b/hive/daemon/daemon.go index bb9267623..33143f776 100644 --- a/hive/daemon/daemon.go +++ b/hive/daemon/daemon.go @@ -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 { diff --git a/hive/handlers/ec2/instance/service_impl.go b/hive/handlers/ec2/instance/service_impl.go index 57cdb6c40..e71cffebf 100644 --- a/hive/handlers/ec2/instance/service_impl.go +++ b/hive/handlers/ec2/instance/service_impl.go @@ -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 } diff --git a/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx b/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx index 971420391..e7a3a27dc 100644 --- a/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx +++ b/hive/services/hiveui/frontend/src/routes/_auth/ec2/(instances)/describe-instances/$id.tsx @@ -114,6 +114,42 @@ function InstanceDetail() { */} + {/* Block Device Mappings */} + {instance.BlockDeviceMappings && + instance.BlockDeviceMappings.length > 0 && ( + + Block Device Mappings + + + + + + + + + )} + {/* AMI Details */} {image && } diff --git a/scripts/reset-dev-env.sh b/scripts/reset-dev-env.sh index 3b214a68d..660bfdc8c 100755 --- a/scripts/reset-dev-env.sh +++ b/scripts/reset-dev-env.sh @@ -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 diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh index 797a208a0..b6ea47192 100755 --- a/scripts/start-dev.sh +++ b/scripts/start-dev.sh @@ -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"