mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
378 lines
15 KiB
Markdown
378 lines
15 KiB
Markdown
# Hive Architecture
|
|
|
|
Hive is an AWS-compatible infrastructure platform for bare-metal, edge, and on-premise deployments. It provides EC2-compatible VM orchestration backed by QEMU/KVM, with storage provided by companion projects Viperblock (EBS) and Predastore (S3).
|
|
|
|
## High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ AWS SDK / CLI │
|
|
│ (aws ec2 run-instances --endpoint-url ...) │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ HTTPS (SigV4 Auth)
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ AWS Gateway (Port 9999) │
|
|
│ │
|
|
│ • TLS termination • AWS SigV4 authentication │
|
|
│ • EC2 Query Protocol parsing • Routes to service handlers │
|
|
│ • XML response generation • NATS client connection │
|
|
│ │
|
|
│ File: hive/services/awsgw/awsgw.go │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ JSON over NATS
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ NATS Message Broker │
|
|
│ │
|
|
│ Topics: Queue Groups: │
|
|
│ • ec2.RunInstances • hive-workers (load balanced) │
|
|
│ • ec2.DescribeInstances • No queue (fan-out to all nodes) │
|
|
│ • ec2.StartInstances │
|
|
│ • ec2.StopInstances │
|
|
│ • ec2.TerminateInstances │
|
|
│ • ebs.mount / ebs.unmount │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌─────────────────┼─────────────────┐
|
|
▼ ▼ ▼
|
|
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
|
│ Hive Daemon │ │ Hive Daemon │ │ Hive Daemon │
|
|
│ (Node 1) │ │ (Node 2) │ │ (Node N) │
|
|
│ │ │ │ │ │
|
|
│ • NATS subscriber │ │ • NATS subscriber │ │ • NATS subscriber │
|
|
│ • Resource manager │ │ • Resource manager │ │ • Resource manager │
|
|
│ • QEMU/KVM launcher │ │ • QEMU/KVM launcher │ │ • QEMU/KVM launcher │
|
|
│ • QMP monitoring │ │ • QMP monitoring │ │ • QMP monitoring │
|
|
│ │ │ │ │ │
|
|
│ File: daemon.go │ │ File: daemon.go │ │ File: daemon.go │
|
|
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
|
│ QEMU/KVM VMs │ │ QEMU/KVM VMs │ │ QEMU/KVM VMs │
|
|
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
|
|
```
|
|
|
|
## Request Flow
|
|
|
|
### 1. AWS SDK Request
|
|
|
|
Users interact with Hive using standard AWS SDKs or the AWS CLI by pointing to a custom endpoint:
|
|
|
|
```bash
|
|
aws --endpoint-url https://localhost:9999 --no-verify-ssl ec2 run-instances \
|
|
--image-id ami-debian12 \
|
|
--instance-type t3.micro \
|
|
--key-name my-keypair
|
|
```
|
|
|
|
The AWS SDK formats this as an HTTP POST with:
|
|
- AWS SigV4 authentication headers
|
|
- EC2 Query Protocol body (Action=RunInstances&ImageId=ami-debian12...)
|
|
|
|
### 2. AWS Gateway
|
|
|
|
The gateway (`hive/services/awsgw/awsgw.go:57-107`) is the entry point:
|
|
|
|
```go
|
|
// awsgw.go:74 - Connect to NATS
|
|
natsConn, err := nats.Connect(nodeConfig.NATS.Host, opts...)
|
|
|
|
// awsgw.go:89-94 - Create gateway with NATS connection
|
|
gw := gateway.GatewayConfig{
|
|
NATSConn: natsConn,
|
|
Config: nodeConfig.AWSGW.Config,
|
|
}
|
|
|
|
// awsgw.go:103 - Start TLS listener
|
|
log.Fatal(app.ListenTLS(nodeConfig.AWSGW.Host, nodeConfig.AWSGW.TLSCert, nodeConfig.AWSGW.TLSKey))
|
|
```
|
|
|
|
Request routing (`hive/gateway/gateway.go:125-156`):
|
|
|
|
1. **Authentication**: SigV4 middleware validates AWS credentials (`gateway.go:104`)
|
|
2. **Service Detection**: Extracts service name (ec2, iam, account) from Authorization header (`gateway.go:158-186`)
|
|
3. **Action Dispatch**: Routes to service-specific handler (`gateway.go:136-145`)
|
|
|
|
```go
|
|
// gateway.go:136-145
|
|
switch svc {
|
|
case "ec2":
|
|
err = gw.EC2_Request(ctx)
|
|
case "account":
|
|
err = gw.Account_Request(ctx)
|
|
case "iam":
|
|
err = gw.IAM_Request(ctx)
|
|
}
|
|
```
|
|
|
|
### 3. EC2 Handler
|
|
|
|
The EC2 handler (`hive/gateway/ec2.go`) parses the Action parameter and delegates to specific handlers:
|
|
|
|
```go
|
|
// ec2.go - Action routing
|
|
switch action {
|
|
case "RunInstances":
|
|
output, err := gateway_ec2_instance.RunInstances(input, gw.NATSConn)
|
|
case "DescribeInstances":
|
|
output, err := gateway_ec2_instance.DescribeInstances(input, gw.NATSConn, expectedNodes)
|
|
// ... more actions
|
|
}
|
|
```
|
|
|
|
### 4. NATS Messaging
|
|
|
|
The gateway communicates with daemons via NATS request/response (`hive/handlers/ec2/instance/service_nats.go:24-57`):
|
|
|
|
```go
|
|
// service_nats.go:24-57
|
|
func (s *NATSInstanceService) RunInstances(input *ec2.RunInstancesInput) (*ec2.Reservation, error) {
|
|
// Marshal input to JSON
|
|
jsonData, err := json.Marshal(input)
|
|
|
|
// Send request to daemon via NATS with 5 minute timeout
|
|
msg, err := s.natsConn.Request("ec2.RunInstances", jsonData, 5*time.Minute)
|
|
|
|
// Unmarshal response
|
|
var reservation ec2.Reservation
|
|
err = json.Unmarshal(msg.Data, &reservation)
|
|
|
|
return &reservation, nil
|
|
}
|
|
```
|
|
|
|
### 5. Daemon Processing
|
|
|
|
Daemons (`hive/daemon/daemon.go`) subscribe to NATS topics and handle requests:
|
|
|
|
```go
|
|
// daemon.go:319 - Subscribe with queue group for load balancing
|
|
d.natsSubscriptions["ec2.RunInstances"], err = d.natsConn.QueueSubscribe(
|
|
"ec2.RunInstances",
|
|
"hive-workers", // Queue group - only one daemon handles each request
|
|
d.handleEC2RunInstances,
|
|
)
|
|
|
|
// daemon.go:373 - Subscribe without queue group for fan-out
|
|
d.natsSubscriptions["ec2.DescribeInstances"], err = d.natsConn.Subscribe(
|
|
"ec2.DescribeInstances",
|
|
d.handleEC2DescribeInstances, // All daemons respond
|
|
)
|
|
```
|
|
|
|
**Queue Groups**: Topics subscribed with a queue group (`hive-workers`) are load-balanced - only one daemon handles each request. Topics without a queue group fan out to all daemons.
|
|
|
|
### 6. VM Launch
|
|
|
|
When a daemon handles `RunInstances`:
|
|
|
|
1. **Resource Check**: Validates CPU/memory availability (`daemon.go:66-74`)
|
|
2. **Volume Generation**: Creates boot, cloud-init, and EFI volumes via Viperblock
|
|
3. **Volume Mount**: Sends `ebs.mount` request to Viperblock, receives NBD URI
|
|
4. **QEMU Launch**: Builds and executes QEMU command with NBD-backed disks
|
|
5. **QMP Monitoring**: Establishes QEMU Machine Protocol connection for VM management
|
|
|
|
```go
|
|
// daemon.go - Volume mounting via NATS
|
|
msg, err := d.natsConn.Request("ebs.mount", ebsMountRequest, 10*time.Second)
|
|
|
|
// daemon.go - QEMU launch with NBD storage
|
|
cmd := exec.Command("qemu-system-x86_64",
|
|
"-drive", fmt.Sprintf("file=nbd:%s,format=raw", nbdURI),
|
|
// ... additional QEMU args
|
|
)
|
|
```
|
|
|
|
## Key Components
|
|
|
|
### Daemon Structure
|
|
|
|
```go
|
|
// daemon.go:76-102
|
|
type Daemon struct {
|
|
node string // Node identifier
|
|
clusterConfig *config.ClusterConfig // Cluster-wide configuration
|
|
config *config.Config // Node-specific configuration
|
|
natsConn *nats.Conn // NATS connection
|
|
resourceMgr *ResourceManager // CPU/Memory tracking
|
|
instanceService *InstanceServiceImpl // EC2 instance operations
|
|
keyService *KeyServiceImpl // SSH key operations
|
|
imageService *ImageServiceImpl // AMI operations
|
|
Instances vm.Instances // Local VMs
|
|
clusterApp *fiber.App // HTTP cluster manager
|
|
}
|
|
```
|
|
|
|
### Resource Manager
|
|
|
|
Tracks available and allocated CPU/memory to prevent overcommit:
|
|
|
|
```go
|
|
// daemon.go:66-74
|
|
type ResourceManager struct {
|
|
mu sync.RWMutex
|
|
availableVCPU int
|
|
availableMem float64
|
|
allocatedVCPU int
|
|
allocatedMem float64
|
|
instanceTypes map[string]InstanceType // t3.micro, t3.small, etc.
|
|
}
|
|
```
|
|
|
|
### NATS Topics
|
|
|
|
| Topic | Queue Group | Purpose |
|
|
|-------|-------------|---------|
|
|
| `ec2.RunInstances` | `hive-workers` | Launch new instances |
|
|
| `ec2.DescribeInstances` | None (fan-out) | Query all nodes for instances |
|
|
| `ec2.StartInstances` | `hive-workers` | Start stopped instances |
|
|
| `ec2.StopInstances` | `hive-workers` | Stop running instances |
|
|
| `ec2.TerminateInstances` | `hive-workers` | Terminate instances |
|
|
| `ec2.CreateKeyPair` | `hive-workers` | Generate SSH keypair |
|
|
| `ec2.DescribeKeyPairs` | `hive-workers` | List SSH keypairs |
|
|
| `ec2.DescribeImages` | `hive-workers` | List AMIs |
|
|
| `ebs.mount` | - | Mount volume via Viperblock |
|
|
| `ebs.unmount` | - | Unmount volume |
|
|
| `hive.admin.{node}.health` | None | Node health checks |
|
|
|
|
### Multi-Node Aggregation
|
|
|
|
For operations that need data from all nodes (like `DescribeInstances`), the gateway uses inbox-based fan-out (`hive/gateway/ec2/instance/DescribeInstances.go`):
|
|
|
|
```go
|
|
// DescribeInstances.go:16-111
|
|
func DescribeInstances(...) {
|
|
// Create unique inbox for collecting responses
|
|
inbox := nats.NewInbox()
|
|
sub, err := natsConn.SubscribeSync(inbox)
|
|
|
|
// Publish to all nodes (no queue group = all daemons respond)
|
|
err = natsConn.PublishRequest("ec2.DescribeInstances", inbox, jsonData)
|
|
|
|
// Collect responses from all nodes with 3-second timeout
|
|
for {
|
|
msg, err := sub.NextMsg(3 * time.Second)
|
|
allReservations = append(allReservations, nodeOutput.Reservations...)
|
|
}
|
|
|
|
return allReservations
|
|
}
|
|
```
|
|
|
|
## Storage Integration
|
|
|
|
### Viperblock (EBS)
|
|
|
|
Block storage volumes are managed via NATS messages to Viperblock:
|
|
|
|
```go
|
|
// Volume mount request
|
|
type EBSRequest struct {
|
|
Name string // Volume name (vol-xxxxx)
|
|
VolType string // gp2, io1, etc.
|
|
Boot bool // Boot volume flag
|
|
EFI bool // EFI boot volume
|
|
CloudInit bool // Cloud-init volume
|
|
NBDURI string // NBD URI returned from mount
|
|
}
|
|
```
|
|
|
|
Viperblock responds with an NBD URI that QEMU uses to access the block device.
|
|
|
|
### Predastore (S3)
|
|
|
|
Object storage used for:
|
|
- AMI image storage
|
|
- SSH public key storage (`/bucket/ec2/{key-name}.pub`)
|
|
- Volume metadata and configuration
|
|
|
|
## Configuration
|
|
|
|
Cluster configuration (`hive/config/config.go`):
|
|
|
|
```go
|
|
type ClusterConfig struct {
|
|
Epoch uint64 // Incremented on config changes
|
|
Node string // This node's identifier
|
|
Version string // Hive version
|
|
Nodes map[string]Config // Configuration for all nodes
|
|
}
|
|
|
|
type Config struct {
|
|
Node, Host, Region, AZ string
|
|
DataDir string
|
|
Daemon DaemonConfig // Cluster manager port
|
|
NATS NATSConfig // NATS broker address
|
|
Predastore PredastoreConfig // S3 endpoint
|
|
AWSGW AWSGWConfig // Gateway port, TLS certs
|
|
AccessKey, SecretKey string
|
|
}
|
|
```
|
|
|
|
## File Structure
|
|
|
|
```
|
|
hive/
|
|
├── hive/
|
|
│ ├── services/
|
|
│ │ └── awsgw/
|
|
│ │ └── awsgw.go # AWS Gateway service entry point
|
|
│ ├── gateway/
|
|
│ │ ├── gateway.go # Request routing, auth middleware
|
|
│ │ ├── ec2.go # EC2 action dispatcher
|
|
│ │ └── ec2/
|
|
│ │ └── instance/
|
|
│ │ ├── RunInstances.go
|
|
│ │ └── DescribeInstances.go
|
|
│ ├── handlers/
|
|
│ │ └── ec2/
|
|
│ │ ├── instance/
|
|
│ │ │ ├── service_nats.go # NATS request/response
|
|
│ │ │ └── service_impl.go # Business logic
|
|
│ │ ├── key/
|
|
│ │ └── image/
|
|
│ ├── daemon/
|
|
│ │ └── daemon.go # Daemon entry point, NATS subscriptions
|
|
│ ├── config/
|
|
│ │ └── config.go # Configuration structures
|
|
│ ├── vm/
|
|
│ │ └── instance.go # VM instance representation
|
|
│ └── qmp/
|
|
│ └── qmp.go # QEMU Machine Protocol client
|
|
└── cmd/
|
|
└── hive/
|
|
└── main.go # CLI entry point
|
|
```
|
|
|
|
## Development
|
|
|
|
### Running the Stack
|
|
|
|
1. Start NATS broker
|
|
2. Start Predastore (S3)
|
|
3. Start Viperblock (EBS)
|
|
4. Start Hive Gateway (`hive awsgw`)
|
|
5. Start Hive Daemon(s) (`hive daemon`)
|
|
|
|
### Testing with AWS CLI
|
|
|
|
```bash
|
|
# Set endpoint
|
|
export AWS_ENDPOINT_URL=https://localhost:9999
|
|
|
|
# Run instance
|
|
aws --no-verify-ssl ec2 run-instances \
|
|
--image-id ami-debian12 \
|
|
--instance-type t3.micro
|
|
|
|
# List instances (queries all nodes)
|
|
aws --no-verify-ssl ec2 describe-instances
|
|
|
|
# Terminate instance
|
|
aws --no-verify-ssl ec2 terminate-instances --instance-ids i-xxxxx
|
|
```
|