mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
Added support for multi-node config settings
This commit is contained in:
+9
-7
@@ -53,8 +53,10 @@ Confirm ./bin/hive exists and executable.
|
||||
|
||||
When running Hive for the first time, run the init function to create the default directories for data, config files and layout required.
|
||||
|
||||
```bash
|
||||
./bin/hive admin init
|
||||
Example single node installation to get started:
|
||||
|
||||
```
|
||||
/bin/hive admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1
|
||||
```
|
||||
|
||||
Next, set the AWS profile to use `hive` which points to the local environment.
|
||||
@@ -93,7 +95,7 @@ aws ec2 create-key-pair \
|
||||
| jq -r '.KeyMaterial | rtrimstr("\n")' > ~/.ssh/hive-key
|
||||
```
|
||||
|
||||
Update permissions to SSH will accept reading the file.
|
||||
Update permissions for the key for ssh to accept reading the file.
|
||||
|
||||
```bash
|
||||
chmod 600 ~/.ssh/hive-key
|
||||
@@ -105,7 +107,7 @@ Next, generate a public key from the specified private key pair.
|
||||
ssh-keygen -y -f ~/.ssh/hive-key > ~/.ssh/hive-key.pub
|
||||
```
|
||||
|
||||
Validate the new key is available
|
||||
### Validate the new key is available
|
||||
|
||||
```bash
|
||||
aws ec2 describe-key-pairs
|
||||
@@ -164,13 +166,13 @@ Extracted image to: /home/ben/hive/images/debian/12/arm64/disk.raw
|
||||
✅ Image import complete. Image-ID (AMI): ami-e29fcc65734aec9ea
|
||||
```
|
||||
|
||||
Next, verify available disk images to confirm the import was successful
|
||||
Next, verify available disk images to confirm the import was successful, replace `ami-XXX` with the value from the command output.
|
||||
|
||||
```bash
|
||||
aws ec2 describe-images --image-ids ami-e29fcc65734aec9ea
|
||||
aws ec2 describe-images --image-ids ami-XXX
|
||||
```
|
||||
|
||||
### Manual import
|
||||
### Manual AMI import
|
||||
|
||||
Using this method you can import any OS disk image. For example, download the Debian 12 image from the repository [https://cloud.debian.org/images/cloud/bookworm/latest/](https://cloud.debian.org/images/cloud/bookworm/latest/)
|
||||
|
||||
|
||||
@@ -26,22 +26,22 @@ Design layout for multi-node configuration.
|
||||
|
||||
```
|
||||
# node1
|
||||
hive admin init --region ap-southeast-2 --availability-zone ap-southeast-2a --nodes 3 --bind :8443 --data-dir ~/hive/
|
||||
hive admin init --region ap-southeast-2 --az ap-southeast-2a --node node1 --nodes 1 --hive-dir ~/hive/
|
||||
|
||||
# node2
|
||||
hive admin join --region ap-southeast-2 --availability-zone ap-southeast-2a --host node1.local:8443 --data-dir ~/hive/
|
||||
hive admin join --region ap-southeast-2 --az ap-southeast-2a --node node2 --host node1.local:8443 --data-dir ~/hive/
|
||||
|
||||
# node3, optionally toggle EBS/EC2/NATs support only
|
||||
hive admin join --region ap-southeast-2 --availability-zone ap-southeast-2a --host node1.local:8443 --data-dir ~/hive/ --cap ebs,ec2,nats
|
||||
hive admin join --region ap-southeast-2 --az ap-southeast-2a --node node3 --host node1.local:8443 --data-dir ~/hive/ --cap ebs,ec2,nats
|
||||
```
|
||||
|
||||
If --host is missing, the join command tries multicast broadcast to find parent node.
|
||||
If --host is missing, the join command tries multicast broadcast to find parent (leader) node.
|
||||
|
||||
Set region with `--region ap-southeast-2` which will create a new Hive cluster for the specified region.
|
||||
|
||||
Set the availability-zone, for production it is recommended to have at least 2 availability-zones defined per region deployment of Hive.
|
||||
For production, it is recommended to run Hive on at least three physical nodes. In this minimum setup, Hive S3 and EBS volumes use 2 data shards and 1 parity shard RS(2,1) to distribute each chunk across the cluster. This configuration tolerates a full node failure while adding only 1.5x storage overhead.
|
||||
|
||||
Overview:
|
||||
### Overview:
|
||||
|
||||
* init creates a cluster-id, node-id, and a short join token, starts a tiny control server on :8443, and writes DNS hint files if available.
|
||||
|
||||
|
||||
+29
-8
@@ -140,6 +140,11 @@ func init() {
|
||||
// Flags for admin init
|
||||
adminInitCmd.Flags().Bool("force", false, "Force re-initialization (overwrites existing config)")
|
||||
adminInitCmd.Flags().String("region", "ap-southeast-2", "Mulga region to create")
|
||||
adminInitCmd.Flags().String("az", "ap-southeast-2a", "Mulga AZ to create")
|
||||
adminInitCmd.Flags().String("node", "node1", "Node name, increment for additional nodes (default, node1)")
|
||||
adminInitCmd.Flags().Int("nodes", 3, "Number of nodes to expect for cluster")
|
||||
adminInitCmd.Flags().String("host", "", "Leader node to join (if not specified, tries multicast discovery)")
|
||||
adminInitCmd.Flags().Int("port", 4432, "Port to bind cluster services on")
|
||||
|
||||
imagesImportCmd.Flags().String("tmp-dir", os.TempDir(), "Temporary directory for image import processing")
|
||||
|
||||
@@ -310,7 +315,7 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
// Calculate the size
|
||||
|
||||
manifest.AMIMetadata.Name = fmt.Sprintf("ami-%s-%s-%s", image.Distro, image.Version, image.Arch)
|
||||
volumeId := viperblock.GenerateVolumeID("ami", manifest.AMIMetadata.Name, appConfig.Predastore.Bucket, time.Now().Unix())
|
||||
volumeId := viperblock.GenerateVolumeID("ami", manifest.AMIMetadata.Name, "", time.Now().Unix()) // TODO: Replace with bucket
|
||||
manifest.AMIMetadata.ImageID = volumeId
|
||||
|
||||
manifest.AMIMetadata.Description = fmt.Sprintf("%s cloud image prepared for Hive", manifest.AMIMetadata.Name)
|
||||
@@ -361,11 +366,11 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) {
|
||||
s3Config := s3.S3Config{
|
||||
VolumeName: volumeId,
|
||||
VolumeSize: uint64(imageStat.Size()),
|
||||
Bucket: appConfig.Predastore.Bucket,
|
||||
Region: appConfig.Predastore.Region,
|
||||
AccessKey: appConfig.AccessKey,
|
||||
SecretKey: appConfig.SecretKey,
|
||||
Host: appConfig.Predastore.Host,
|
||||
Bucket: appConfig.Nodes[appConfig.Node].Predastore.Bucket,
|
||||
Region: appConfig.Nodes[appConfig.Node].Predastore.Region,
|
||||
AccessKey: appConfig.Nodes[appConfig.Node].AccessKey,
|
||||
SecretKey: appConfig.Nodes[appConfig.Node].SecretKey,
|
||||
Host: appConfig.Nodes[appConfig.Node].Predastore.Host,
|
||||
}
|
||||
|
||||
vbConfig := viperblock.VB{
|
||||
@@ -435,6 +440,11 @@ func runAdminInit(cmd *cobra.Command, args []string) {
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
configDir, _ := cmd.Flags().GetString("config-dir")
|
||||
region, _ := cmd.Flags().GetString("region")
|
||||
az, _ := cmd.Flags().GetString("az")
|
||||
node, _ := cmd.Flags().GetString("node")
|
||||
port, _ := cmd.Flags().GetInt("port")
|
||||
|
||||
fmt.Println("Config", az, node, port)
|
||||
|
||||
// Default config directory
|
||||
if configDir == "" {
|
||||
@@ -529,8 +539,10 @@ func runAdminInit(cmd *cobra.Command, args []string) {
|
||||
{filepath.Join(natsDir, "nats.conf"), natsConfTemplate, "nats/nats.conf"},
|
||||
}
|
||||
|
||||
portStr := fmt.Sprintf("%d", port)
|
||||
|
||||
for _, cfg := range configs {
|
||||
if err := generateConfigFile(cfg.path, cfg.template, accessKey, secretKey, accountID, region, natsToken, hiveRoot); err != nil {
|
||||
if err := generateConfigFile(cfg.path, cfg.template, accessKey, secretKey, accountID, region, natsToken, hiveRoot, az, node, portStr); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating %s: %v\n", cfg.name, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -693,7 +705,7 @@ func generateSelfSignedCert(certPath, keyPath string) error {
|
||||
}
|
||||
|
||||
// generateConfigFile creates a configuration file from a template
|
||||
func generateConfigFile(path, templateContent, accessKey, secretKey, accountID, region, natsToken, dataDir string) error {
|
||||
func generateConfigFile(path, templateContent, accessKey, secretKey, accountID, region, natsToken, dataDir, Az, Node, Port string) error {
|
||||
// Parse the embedded template
|
||||
tmpl, err := template.New("config").Parse(templateContent)
|
||||
if err != nil {
|
||||
@@ -708,6 +720,11 @@ func generateConfigFile(path, templateContent, accessKey, secretKey, accountID,
|
||||
Region string
|
||||
NatsToken string
|
||||
DataDir string
|
||||
|
||||
// Add more fields as needed
|
||||
Node string
|
||||
Az string
|
||||
Port string
|
||||
}{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
@@ -715,6 +732,10 @@ func generateConfigFile(path, templateContent, accessKey, secretKey, accountID,
|
||||
Region: region,
|
||||
NatsToken: natsToken,
|
||||
DataDir: dataDir,
|
||||
|
||||
Node: Node,
|
||||
Az: Az,
|
||||
Port: Port,
|
||||
}
|
||||
|
||||
// Create file with secure permissions
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
appConfig *config.Config
|
||||
appConfig *config.ClusterConfig
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
|
||||
+39
-27
@@ -167,7 +167,8 @@ var viperblockStartCmd = &cobra.Command{
|
||||
fmt.Println("Loading config from:", cfgFile)
|
||||
|
||||
// TODO: Support ENV vars, CLI, otherwise revert to config.LoadConfig()
|
||||
appConfig, err := config.LoadConfig(cfgFile)
|
||||
clusterConfig, err := config.LoadConfig(cfgFile)
|
||||
nodeConfig := clusterConfig.Nodes[clusterConfig.Node]
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error loading config file:", err)
|
||||
@@ -178,48 +179,51 @@ var viperblockStartCmd = &cobra.Command{
|
||||
|
||||
if natsHost != "" {
|
||||
fmt.Println("Overwriting natsHost:", natsHost)
|
||||
appConfig.NATS.Host = natsHost
|
||||
nodeConfig.NATS.Host = natsHost
|
||||
}
|
||||
|
||||
s3Host := viper.GetString("s3-host")
|
||||
|
||||
if s3Host != "" {
|
||||
fmt.Println("Overwriting s3host:", s3Host)
|
||||
appConfig.Predastore.Host = s3Host
|
||||
nodeConfig.Predastore.Host = s3Host
|
||||
}
|
||||
|
||||
s3Bucket := viper.GetString("s3-bucket")
|
||||
|
||||
if s3Bucket != "" {
|
||||
fmt.Println("Overwriting s3bucket:", s3Bucket)
|
||||
appConfig.Predastore.Bucket = s3Bucket
|
||||
nodeConfig.Predastore.Bucket = s3Bucket
|
||||
}
|
||||
|
||||
s3Region := viper.GetString("s3-region")
|
||||
|
||||
if s3Region != "" {
|
||||
fmt.Println("Overwriting s3Region:", s3Region)
|
||||
appConfig.Predastore.Region = s3Region
|
||||
nodeConfig.Predastore.Region = s3Region
|
||||
}
|
||||
|
||||
accessKey := viper.GetString("access-key")
|
||||
if accessKey != "" {
|
||||
fmt.Println("Overwriting access-key:", accessKey)
|
||||
appConfig.Predastore.AccessKey = accessKey
|
||||
nodeConfig.Predastore.AccessKey = accessKey
|
||||
}
|
||||
|
||||
secretKey := viper.GetString("secret-key")
|
||||
if secretKey != "" {
|
||||
fmt.Println("Overwriting secret-key:", secretKey)
|
||||
appConfig.Predastore.SecretKey = secretKey
|
||||
nodeConfig.Predastore.SecretKey = secretKey
|
||||
}
|
||||
|
||||
baseDir := viper.GetString("base-dir")
|
||||
if baseDir != "" {
|
||||
fmt.Println("Overwriting base-dir:", baseDir)
|
||||
appConfig.Predastore.BaseDir = baseDir
|
||||
nodeConfig.Predastore.BaseDir = baseDir
|
||||
}
|
||||
|
||||
// Apply changes back to cluster config
|
||||
clusterConfig.Nodes[clusterConfig.Node] = nodeConfig
|
||||
|
||||
pluginPath := viper.GetString("plugin-path")
|
||||
|
||||
if pluginPath == "" {
|
||||
@@ -236,14 +240,14 @@ var viperblockStartCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
service, err := service.New("viperblock", &viperblockd.Config{
|
||||
NatsHost: appConfig.NATS.Host,
|
||||
NatsHost: nodeConfig.NATS.Host,
|
||||
PluginPath: pluginPath,
|
||||
S3Host: appConfig.Predastore.Host,
|
||||
Bucket: appConfig.Predastore.Bucket,
|
||||
Region: appConfig.Predastore.Region,
|
||||
AccessKey: appConfig.Predastore.AccessKey,
|
||||
SecretKey: appConfig.Predastore.SecretKey,
|
||||
BaseDir: appConfig.Predastore.BaseDir,
|
||||
S3Host: nodeConfig.Predastore.Host,
|
||||
Bucket: nodeConfig.Predastore.Bucket,
|
||||
Region: nodeConfig.Predastore.Region,
|
||||
AccessKey: nodeConfig.Predastore.AccessKey,
|
||||
SecretKey: nodeConfig.Predastore.SecretKey,
|
||||
BaseDir: nodeConfig.Predastore.BaseDir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -363,7 +367,8 @@ var hiveStartCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// TODO: Support ENV vars, CLI, otherwise revert to config.LoadConfig()
|
||||
appConfig, err := config.LoadConfig(cfgFile)
|
||||
clusterConfig, err := config.LoadConfig(cfgFile)
|
||||
nodeConfig := clusterConfig.Nodes[clusterConfig.Node]
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error loading config file:", err)
|
||||
@@ -375,7 +380,7 @@ var hiveStartCmd = &cobra.Command{
|
||||
|
||||
if baseDir != "" {
|
||||
fmt.Println("Overwriting base-dir to:", baseDir)
|
||||
appConfig.BaseDir = baseDir
|
||||
nodeConfig.BaseDir = baseDir
|
||||
}
|
||||
|
||||
// Overwrite defaults (CLI first, config second, env third)
|
||||
@@ -383,10 +388,13 @@ var hiveStartCmd = &cobra.Command{
|
||||
|
||||
if walDir != "" {
|
||||
fmt.Println("Overwriting wal-dir to:", walDir)
|
||||
appConfig.WalDir = walDir
|
||||
nodeConfig.WalDir = walDir
|
||||
}
|
||||
|
||||
service, err := service.New("hive", appConfig)
|
||||
// Apply changes back to cluster config
|
||||
clusterConfig.Nodes[clusterConfig.Node] = nodeConfig
|
||||
|
||||
service, err := service.New("hive", clusterConfig)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error starting hive service:", err)
|
||||
@@ -404,7 +412,7 @@ var hiveStopCmd = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Stopping hive service...")
|
||||
|
||||
service, err := service.New("hive", &config.Config{})
|
||||
service, err := service.New("hive", &config.ClusterConfig{})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error stopping hive service:", err)
|
||||
@@ -443,7 +451,8 @@ var awsgwStartCmd = &cobra.Command{
|
||||
fmt.Println("Loading config from:", cfgFile)
|
||||
|
||||
// TODO: Support ENV vars, CLI, otherwise revert to config.LoadConfig()
|
||||
appConfig, err := config.LoadConfig(cfgFile)
|
||||
clusterConfig, err := config.LoadConfig(cfgFile)
|
||||
nodeConfig := clusterConfig.Nodes[clusterConfig.Node]
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error loading config file:", err)
|
||||
@@ -454,30 +463,33 @@ var awsgwStartCmd = &cobra.Command{
|
||||
awsgwHost := viper.GetString("host")
|
||||
if awsgwHost != "" {
|
||||
fmt.Println("Overwriting awsgw host to:", awsgwHost)
|
||||
appConfig.AWSGW.Host = awsgwHost
|
||||
nodeConfig.AWSGW.Host = awsgwHost
|
||||
}
|
||||
|
||||
awsgwTlsCert := viper.GetString("tls-cert")
|
||||
if awsgwTlsCert != "" {
|
||||
fmt.Println("Overwriting awsgw tls-cert to:", awsgwTlsCert)
|
||||
appConfig.AWSGW.TLSCert = awsgwTlsCert
|
||||
nodeConfig.AWSGW.TLSCert = awsgwTlsCert
|
||||
}
|
||||
|
||||
awsgwTlsKey := viper.GetString("tls-key")
|
||||
|
||||
if awsgwTlsKey != "" {
|
||||
fmt.Println("Overwriting awsgw tls-key to:", awsgwTlsKey)
|
||||
appConfig.AWSGW.TLSKey = awsgwTlsKey
|
||||
nodeConfig.AWSGW.TLSKey = awsgwTlsKey
|
||||
}
|
||||
|
||||
baseDir := viper.GetString("base-dir")
|
||||
|
||||
if awsgwTlsKey != "" {
|
||||
fmt.Println("Overwriting awsgw base-dir to:", baseDir)
|
||||
appConfig.BaseDir = baseDir
|
||||
nodeConfig.BaseDir = baseDir
|
||||
}
|
||||
|
||||
service, err := service.New("awsgw", appConfig)
|
||||
// Apply changes back to cluster config
|
||||
clusterConfig.Nodes[clusterConfig.Node] = nodeConfig
|
||||
|
||||
service, err := service.New("awsgw", clusterConfig)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error starting awsgw service:", err)
|
||||
@@ -495,7 +507,7 @@ var awsgwStopCmd = &cobra.Command{
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Stopping awsgw service...")
|
||||
|
||||
service, err := service.New("awsgw", &config.Config{})
|
||||
service, err := service.New("awsgw", &config.ClusterConfig{})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error stopping awsgw service:", err)
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
# Domain configuration file in TOML format.
|
||||
version = "1.0"
|
||||
region = "{{.Region}}"
|
||||
account_id = "{{.AccountID}}"
|
||||
epoch = 1
|
||||
node = "{{.Node}}"
|
||||
|
||||
[nodes.{{.Node}}]
|
||||
node = "{{.Node}}"
|
||||
region = "{{.Region}}"
|
||||
az = "{{.Az}}"
|
||||
|
||||
account_id = "{{.AccountID}}"
|
||||
access_key = "{{.AccessKey}}"
|
||||
secret_key = "{{.SecretKey}}"
|
||||
|
||||
[daemon]
|
||||
host = "0.0.0.0:4432"
|
||||
[nodes.{{.Node}}.daemon]
|
||||
host = "0.0.0.0:{{.Port}}"
|
||||
tlskey = "config/server.key"
|
||||
tlscert = "config/server.pem"
|
||||
|
||||
[awsgw]
|
||||
[nodes.{{.Node}}.awsgw]
|
||||
host = "0.0.0.0:9999"
|
||||
tlskey = "config/server.key"
|
||||
tlscert = "config/server.pem"
|
||||
debug = true
|
||||
config = "awsgw/awsgw.toml"
|
||||
|
||||
[nats]
|
||||
[nodes.{{.Node}}.nats]
|
||||
host = "0.0.0.0:4222"
|
||||
|
||||
[nats.acl]
|
||||
[nodes.{{.Node}}.nats.acl]
|
||||
token = "{{.NatsToken}}"
|
||||
|
||||
[nats.sub]
|
||||
[nodes.{{.Node}}.nats.sub]
|
||||
|
||||
[predastore]
|
||||
[nodes.{{.Node}}.predastore]
|
||||
host = "0.0.0.0:8443"
|
||||
region = "{{.Region}}"
|
||||
bucket = "predastore"
|
||||
|
||||
+18
-70
@@ -11,17 +11,31 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ClusterConfig struct {
|
||||
Epoch uint64 `mapstructure:"epoch"` // bump when leader commits changes
|
||||
Node string `mapstructure:"node"` // my node name
|
||||
Version string `mapstructure:"version"` // hive version
|
||||
Nodes map[string]Config `mapstructure:"nodes"` // full config for every node
|
||||
}
|
||||
|
||||
// Config holds all configuration for the application
|
||||
type Config struct {
|
||||
// Node config
|
||||
Node string `mapstructure:"node"`
|
||||
Host string `mapstructure:"host"` // Unique hostname or IP of this node
|
||||
Region string `mapstructure:"region"`
|
||||
AZ string `mapstructure:"az"`
|
||||
DataDir string `mapstructure:"data_dir"`
|
||||
|
||||
Daemon DaemonConfig `mapstructure:"daemon"`
|
||||
NATS NATSConfig `mapstructure:"nats"`
|
||||
Predastore PredastoreConfig `mapstructure:"predastore"`
|
||||
AWSGW AWSGWConfig `mapstructure:"awsgw"`
|
||||
|
||||
// Authentication
|
||||
// TODO: Move to more appropriate setting above
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Host string `mapstructure:"host"`
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
WalDir string `mapstructure:"wal_dir"`
|
||||
}
|
||||
@@ -33,7 +47,7 @@ type AWSGWConfig struct {
|
||||
Config string `mapstructure:"config"`
|
||||
|
||||
Debug bool `mapstructure:"debug"`
|
||||
ExpectedNodes int `mapstructure:"expected_nodes"`
|
||||
ExpectedNodes int `mapstructure:"expected_nodes"` // TODO: Replace with root cluster config
|
||||
}
|
||||
|
||||
type PredastoreConfig struct {
|
||||
@@ -106,25 +120,6 @@ type EBSDeleteResponse struct {
|
||||
}
|
||||
|
||||
// EC2, TODO: Move to vm.go or more applicable place
|
||||
type EC2Response struct {
|
||||
InstanceID string
|
||||
Hostname string
|
||||
Success bool
|
||||
Status string
|
||||
Error string
|
||||
}
|
||||
|
||||
type EC2DescribeRequest struct {
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
type EC2DescribeResponse struct {
|
||||
InstanceID string
|
||||
Hostname string
|
||||
Status string
|
||||
Error string
|
||||
}
|
||||
|
||||
type EC2StartInstancesRequest struct {
|
||||
InstanceID string
|
||||
}
|
||||
@@ -146,45 +141,14 @@ func (ec2StartInstanceResponse EC2StartInstancesResponse) Respond(msg *nats.Msg)
|
||||
|
||||
msg.Respond(response)
|
||||
|
||||
}
|
||||
func (ec2DescribeResponse EC2DescribeResponse) Respond(msg *nats.Msg) {
|
||||
|
||||
response, err := json.Marshal(ec2DescribeResponse)
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal response", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg.Respond(response)
|
||||
|
||||
}
|
||||
|
||||
func (ec2Response EC2Response) Respond(msg *nats.Msg) {
|
||||
|
||||
response, err := json.Marshal(ec2Response)
|
||||
if err != nil {
|
||||
slog.Error("Failed to marshal response", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg.Respond(response)
|
||||
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration from file and environment variables
|
||||
func LoadConfig(configPath string) (*Config, error) {
|
||||
func LoadConfig(configPath string) (*ClusterConfig, error) {
|
||||
// Set environment variable prefix
|
||||
viper.SetEnvPrefix("HIVE")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Set default values
|
||||
//viper.SetDefault("host", "https://localhost:8443/")
|
||||
//viper.SetDefault("base_dir", "/tmp/vb/")
|
||||
|
||||
//viper.SetDefault("daemon.host", "0.0.0.0:4432")
|
||||
//viper.SetDefault("nats.host", "0.0.0.0:4222")
|
||||
//viper.SetDefault("nats.sub.subject", "ec2.>")
|
||||
|
||||
// Try to load config file if it exists
|
||||
if configPath != "" {
|
||||
// Check if file exists
|
||||
@@ -202,26 +166,10 @@ func LoadConfig(configPath string) (*Config, error) {
|
||||
}
|
||||
|
||||
// Create config struct
|
||||
var config Config
|
||||
var config ClusterConfig
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling config: %w", err)
|
||||
}
|
||||
|
||||
// Validate required fields (can be overwritten by env vars)
|
||||
/*
|
||||
if config.NATS.Host == "" {
|
||||
return nil, fmt.Errorf("NATS host is required")
|
||||
}
|
||||
|
||||
if config.AccessKey == "" {
|
||||
return nil, fmt.Errorf("access key is required")
|
||||
}
|
||||
if config.SecretKey == "" {
|
||||
return nil, fmt.Errorf("secret key is required")
|
||||
}
|
||||
*/
|
||||
|
||||
// fmt.Println("Config: ", config)
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
+11
-37
@@ -71,6 +71,8 @@ type ResourceManager struct {
|
||||
|
||||
// Daemon represents the main daemon service
|
||||
type Daemon struct {
|
||||
node string
|
||||
clusterConfig *config.ClusterConfig
|
||||
config *config.Config
|
||||
natsConn *nats.Conn
|
||||
resourceMgr *ResourceManager
|
||||
@@ -178,16 +180,21 @@ func NewResourceManager() *ResourceManager {
|
||||
}
|
||||
|
||||
// NewDaemon creates a new daemon instance
|
||||
func NewDaemon(cfg *config.Config) *Daemon {
|
||||
func NewDaemon(cfg *config.ClusterConfig) *Daemon {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// If WalDir is not set, use BaseDir
|
||||
if cfg.WalDir == "" {
|
||||
cfg.WalDir = cfg.BaseDir
|
||||
config := cfg.Nodes[cfg.Node]
|
||||
if cfg.Nodes[cfg.Node].WalDir == "" {
|
||||
config.WalDir = config.BaseDir
|
||||
|
||||
cfg.Nodes[cfg.Node] = config
|
||||
}
|
||||
|
||||
return &Daemon{
|
||||
config: cfg,
|
||||
node: cfg.Node,
|
||||
clusterConfig: cfg,
|
||||
config: &config,
|
||||
resourceMgr: NewResourceManager(),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
@@ -488,39 +495,6 @@ func (d *Daemon) handleEC2StartInstances(msg *nats.Msg) {
|
||||
|
||||
}
|
||||
|
||||
func (d *Daemon) handleEC2Describe(msg *nats.Msg) {
|
||||
|
||||
var ec2DescribeRequest config.EC2DescribeRequest
|
||||
|
||||
if err := json.Unmarshal(msg.Data, &ec2DescribeRequest); err != nil {
|
||||
log.Printf("Error unmarshaling EC2 describe request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("EC2 Describe Request", "instanceId", ec2DescribeRequest.InstanceID)
|
||||
|
||||
var ec2DescribeResponse config.EC2DescribeResponse
|
||||
|
||||
// Check if the instance is running on this node
|
||||
d.Instances.Mu.Lock()
|
||||
defer d.Instances.Mu.Unlock()
|
||||
|
||||
instance, ok := d.Instances.VMS[ec2DescribeRequest.InstanceID]
|
||||
|
||||
if !ok {
|
||||
slog.Error("EC2 Describe Request - Instance not found", "instanceId", ec2DescribeRequest.InstanceID)
|
||||
ec2DescribeResponse.InstanceID = ec2DescribeRequest.InstanceID
|
||||
ec2DescribeResponse.Error = fmt.Sprintf("Instance %s not found", ec2DescribeRequest.InstanceID)
|
||||
ec2DescribeResponse.Respond(msg)
|
||||
return
|
||||
}
|
||||
|
||||
ec2DescribeResponse.InstanceID = instance.ID
|
||||
ec2DescribeResponse.Status = instance.Status
|
||||
ec2DescribeResponse.Respond(msg)
|
||||
|
||||
}
|
||||
|
||||
func (d *Daemon) SendQMPCommand(q *qmp.QMPClient, cmd qmp.QMPCommand, instanceId string) (*qmp.QMPResponse, error) {
|
||||
|
||||
// Confirm QMP client is initialized
|
||||
|
||||
@@ -55,6 +55,12 @@ func createTestDaemon(t *testing.T, natsURL string) *Daemon {
|
||||
os.RemoveAll(tmpDir)
|
||||
})
|
||||
|
||||
// New cluster config
|
||||
clusterCfg := &config.ClusterConfig{
|
||||
Node: "node-1",
|
||||
Nodes: map[string]config.Config{},
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
BaseDir: tmpDir,
|
||||
WalDir: tmpDir,
|
||||
@@ -76,7 +82,9 @@ func createTestDaemon(t *testing.T, natsURL string) *Daemon {
|
||||
SecretKey: "test-secret-key",
|
||||
}
|
||||
|
||||
daemon := NewDaemon(cfg)
|
||||
clusterCfg.Nodes["node-1"] = *cfg
|
||||
|
||||
daemon := NewDaemon(clusterCfg)
|
||||
|
||||
// Connect to NATS
|
||||
nc, err := nats.Connect(natsURL)
|
||||
@@ -352,6 +360,12 @@ func TestDaemon_Initialization(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// New cluster config
|
||||
clusterCfg := &config.ClusterConfig{
|
||||
Node: "node-1",
|
||||
Nodes: map[string]config.Config{},
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
BaseDir: tmpDir,
|
||||
WalDir: tmpDir,
|
||||
@@ -362,7 +376,9 @@ func TestDaemon_Initialization(t *testing.T) {
|
||||
SecretKey: "test-secret",
|
||||
}
|
||||
|
||||
daemon := NewDaemon(cfg)
|
||||
clusterCfg.Nodes["node-1"] = *cfg
|
||||
|
||||
daemon := NewDaemon(clusterCfg)
|
||||
|
||||
assert.NotNil(t, daemon)
|
||||
assert.NotNil(t, daemon.resourceMgr)
|
||||
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
var serviceName = "awsgw"
|
||||
|
||||
type Service struct {
|
||||
Config *config.Config
|
||||
Config *config.ClusterConfig
|
||||
}
|
||||
|
||||
func New(cfg interface{}) (svc *Service, err error) {
|
||||
svc = &Service{
|
||||
Config: cfg.(*config.Config),
|
||||
Config: cfg.(*config.ClusterConfig),
|
||||
}
|
||||
return svc, nil
|
||||
}
|
||||
@@ -54,11 +54,13 @@ func (svc *Service) Reload() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func launchService(config *config.Config) (err error) {
|
||||
func launchService(config *config.ClusterConfig) (err error) {
|
||||
|
||||
nodeConfig := config.Nodes[config.Node]
|
||||
|
||||
// Connect to NATS for service communication
|
||||
opts := []nats.Option{
|
||||
nats.Token(config.NATS.ACL.Token),
|
||||
nats.Token(nodeConfig.NATS.ACL.Token),
|
||||
nats.ReconnectWait(time.Second),
|
||||
nats.MaxReconnects(-1), // Infinite reconnects
|
||||
nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
|
||||
@@ -69,26 +71,26 @@ func launchService(config *config.Config) (err error) {
|
||||
}),
|
||||
}
|
||||
|
||||
natsConn, err := nats.Connect(config.NATS.Host, opts...)
|
||||
natsConn, err := nats.Connect(nodeConfig.NATS.Host, opts...)
|
||||
if err != nil {
|
||||
slog.Error("Failed to connect to NATS", "err", err)
|
||||
return err
|
||||
}
|
||||
defer natsConn.Close()
|
||||
|
||||
slog.Info("Connected to NATS server", "host", config.NATS.Host)
|
||||
slog.Info("Connected to NATS server", "host", nodeConfig.NATS.Host)
|
||||
|
||||
// Append Base dir if config has no leading path
|
||||
if config.BaseDir != "" && !strings.HasPrefix(config.AWSGW.Config, "/") {
|
||||
config.AWSGW.Config = fmt.Sprintf("%s/%s", config.BaseDir, config.AWSGW.Config)
|
||||
if nodeConfig.BaseDir != "" && !strings.HasPrefix(nodeConfig.AWSGW.Config, "/") {
|
||||
nodeConfig.AWSGW.Config = fmt.Sprintf("%s/%s", nodeConfig.BaseDir, nodeConfig.AWSGW.Config)
|
||||
}
|
||||
|
||||
// Create gateway with NATS connection
|
||||
gw := gateway.GatewayConfig{
|
||||
Debug: config.AWSGW.Debug,
|
||||
Debug: nodeConfig.AWSGW.Debug,
|
||||
DisableLogging: false,
|
||||
NATSConn: natsConn,
|
||||
Config: config.AWSGW.Config,
|
||||
Config: nodeConfig.AWSGW.Config,
|
||||
}
|
||||
|
||||
app := gw.SetupRoutes()
|
||||
@@ -98,7 +100,7 @@ func launchService(config *config.Config) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Fatal(app.ListenTLS(config.AWSGW.Host, config.AWSGW.TLSCert, config.AWSGW.TLSKey))
|
||||
log.Fatal(app.ListenTLS(nodeConfig.AWSGW.Host, nodeConfig.AWSGW.TLSCert, nodeConfig.AWSGW.TLSKey))
|
||||
|
||||
return nil
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
var serviceName = "hive"
|
||||
|
||||
type Service struct {
|
||||
Config *config.Config
|
||||
Config *config.ClusterConfig
|
||||
}
|
||||
|
||||
func New(cfg interface{}) (svc *Service, err error) {
|
||||
svc = &Service{
|
||||
Config: cfg.(*config.Config),
|
||||
Config: cfg.(*config.ClusterConfig),
|
||||
}
|
||||
return svc, nil
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (svc *Service) Reload() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func launchService(config *config.Config) (err error) {
|
||||
func launchService(config *config.ClusterConfig) (err error) {
|
||||
|
||||
d := daemon.NewDaemon(config)
|
||||
slog.Info("Starting Hive daemon ...")
|
||||
|
||||
Reference in New Issue
Block a user