diff --git a/.forgejo/workflows/prerelease.yml b/.forgejo/workflows/prerelease.yml index 1869374..2440fbd 100644 --- a/.forgejo/workflows/prerelease.yml +++ b/.forgejo/workflows/prerelease.yml @@ -33,7 +33,9 @@ jobs: goos: [linux] goarch: [amd64] binaries: + - db - metadata + - metacli - agent uses: ./.forgejo/workflows/build.yml with: diff --git a/cmd/metacli/main.go b/cmd/metacli/main.go new file mode 100644 index 0000000..140bde6 --- /dev/null +++ b/cmd/metacli/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + "fmt" + + configuration "git.g3e.fr/syonad/two/internal/config/agent" + "git.g3e.fr/syonad/two/internal/metadata" + "git.g3e.fr/syonad/two/pkg/db/kv" +) + +func main() { + conf_file := flag.String("conf", "/etc/two/agent.yml", "configuration file") + vm_name := flag.String("vm_name", "", "Nom de la vm") + vpc := flag.String("vpc_name", "", "vpc name") + bind_ip := flag.String("ip", "", "bind ip") + bind_port := flag.String("port", "", "bind port") + ssh_key := flag.String("key", "", "Clef ssh") + password := flag.String("pass", "", "password user") + start := flag.Bool("start", false, "start metadata server") + stop := flag.Bool("stop", false, "stop metadata server") + dryrun := flag.Bool("dryrun", false, "launch in dry node") + + flag.Parse() + + conf, err := configuration.LoadConfig(*conf_file) + if err != nil { + fmt.Println(err) + return + } + + db := kv.InitDB(kv.Config{ + Path: conf.Database.Path, + }, false) + defer db.Close() + + if *start { + if err := metadata.StartMetadata(metadata.NoCloudConfig{ + VpcName: *vpc, + Name: *vm_name, + BindIP: *bind_ip, + BindPort: *bind_port, + Password: *password, + SSHKEY: *ssh_key, + }, db, *dryrun); err != nil { + fmt.Println(err) + } + } else if *stop { + if err := metadata.StopMetadata(*vm_name, db, *dryrun); err != nil { + fmt.Println(err) + } + } +} diff --git a/cmd/metadata/main.go b/cmd/metadata/main.go index 82abaed..96e3f75 100644 --- a/cmd/metadata/main.go +++ b/cmd/metadata/main.go @@ -2,29 +2,26 @@ package main import ( "flag" - "fmt" - "os" - configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/metadata" ) var ( - confFile = flag.String("conf", "/etc/two/agent.yml", "configuration file") - vm_name = flag.String("vm", "", "Name of the vm") + iface = flag.String("interface", "0.0.0.0", "Interface IP à écouter") + port = flag.Int("port", 0, "Port à utiliser") + netns_name = flag.String("netns", "", "Network namespace à utiliser") + conf_file = flag.String("conf", "/etc/two/agent.yml", "configuration file") + vm_name = flag.String("vm", "", "Name of the vm") ) func main() { flag.Parse() - cfg, err := configuration.LoadConfig(*confFile) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err) - os.Exit(1) - } - metadata.StartServer(metadata.ServerConfig{ - VmName: *vm_name, - RunDir: cfg.Metadata.RunDir, + Netns: *netns_name, + Iface: *iface, + Port: *port, + ConfFile: *conf_file, + VmName: *vm_name, }) } diff --git a/conf/agent/config.exemple.yml b/conf/agent/config.exemple.yml index 89eb90f..fb8e604 100644 --- a/conf/agent/config.exemple.yml +++ b/conf/agent/config.exemple.yml @@ -35,10 +35,6 @@ interfaces: internet: br-000000 admin: br-000000 -# Metadata server runtime directory (cloud-init files per VM) -metadata: - run_dir: "/run/two/metadata" - # Logging configuration logger: # Log level: debug, info, warn, error (default: info) diff --git a/internal/config/agent/struct.go b/internal/config/agent/struct.go index 69a4492..d8e4ee5 100644 --- a/internal/config/agent/struct.go +++ b/internal/config/agent/struct.go @@ -28,9 +28,6 @@ type Config struct { Level string `mapstructure:"level"` Debug bool `mapstructure:"debug"` } `mapstructure:"logger"` - Metadata struct { - RunDir string `mapstructure:"run_dir"` - } `mapstructure:"metadata"` DefaultInterface string `mapstructure:"default_interface"` Interfaces map[string]string `mapstructure:"interfaces"` } @@ -49,7 +46,6 @@ func LoadConfig(path string) (*Config, error) { v.SetDefault("worker.buffer_size", 100) v.SetDefault("dispatcher.timeout_seconds", 300) v.SetDefault("dispatcher.poll_seconds", 2) - v.SetDefault("metadata.run_dir", "/run/two/metadata") v.SetDefault("default_interface", "br-000000") v.SetDefault("logger.level", "info") v.SetDefault("logger.debug", false) diff --git a/internal/dispatcher/agent/vm_commands.go b/internal/dispatcher/agent/vm_commands.go index aade998..c0245ca 100644 --- a/internal/dispatcher/agent/vm_commands.go +++ b/internal/dispatcher/agent/vm_commands.go @@ -66,7 +66,7 @@ func (c StartVMCommand) Execute(db *badger.DB, cfg *configuration.Config) error case <-time.After(time.Duration(cfg.Dispatcher.PollSeconds) * time.Second): } } - return vm.StartVM(db, c.Name, cfg) + return vm.StartVM(db, c.Name) } type StopVMCommand struct { diff --git a/internal/metadata/handle.go b/internal/metadata/handle.go index d34dd20..9ec5486 100644 --- a/internal/metadata/handle.go +++ b/internal/metadata/handle.go @@ -3,18 +3,18 @@ package metadata import ( "fmt" - configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/pkg/systemd" + "github.com/dgraph-io/badger/v4" ) -func StartMetadata(config NoCloudConfig, cfg *configuration.Config, dryrun bool) error { +func StartMetadata(config NoCloudConfig, db *badger.DB, dryrun bool) error { service, err := systemd.New() if err != nil { return fmt.Errorf("failed to connect to systemd: %w", err) } defer service.Close() - LoadNcCloudInDB(config, cfg.Metadata.RunDir) + LoadNcCloudInDB(config, db) if !dryrun { if err := service.Start("metadata@" + config.Name + ".service"); err != nil { return fmt.Errorf("failed to start metadata@%s: %w", config.Name, err) @@ -23,17 +23,17 @@ func StartMetadata(config NoCloudConfig, cfg *configuration.Config, dryrun bool) return nil } -func StopMetadata(vmName string, cfg *configuration.Config, dryrun bool) error { +func StopMetadata(vm_name string, db *badger.DB, dryrun bool) error { service, err := systemd.New() if err != nil { return fmt.Errorf("failed to connect to systemd: %w", err) } defer service.Close() - UnLoadNoCloudInDB(vmName, cfg.Metadata.RunDir) + UnLoadNoCloudInDB(vm_name, db) if !dryrun { - if err := service.Stop("metadata@" + vmName + ".service"); err != nil { - return fmt.Errorf("failed to stop metadata@%s: %w", vmName, err) + if err := service.Stop("metadata@" + vm_name + ".service"); err != nil { + return fmt.Errorf("failed to stop metadata@%s: %w", vm_name, err) } } return nil diff --git a/internal/metadata/metadata_test.go b/internal/metadata/metadata_test.go index cc54530..5884151 100644 --- a/internal/metadata/metadata_test.go +++ b/internal/metadata/metadata_test.go @@ -3,10 +3,10 @@ package metadata import ( "net/http" "net/http/httptest" - "os" - "path/filepath" "strings" "testing" + + "git.g3e.fr/syonad/two/pkg/db/kv" ) func newCfg() NoCloudConfig { @@ -20,9 +20,11 @@ func newCfg() NoCloudConfig { } } -func useTestDir(t *testing.T) string { +func newTestDB(t *testing.T) interface{ Close() error } { t.Helper() - return t.TempDir() + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) + return db } // --- RenderConfig --- @@ -106,67 +108,78 @@ func TestRenderConfig_SpecialCharsInName(t *testing.T) { // --- LoadNcCloudInDB / UnLoadNoCloudInDB --- -func readTestFile(t *testing.T, dir, vmName, name string) string { - t.Helper() - b, err := os.ReadFile(filepath.Join(dir, vmName, name)) - if err != nil { - t.Errorf("fichier %q absent après LoadNcCloudInDB : %v", name, err) - return "" +func TestLoadNcCloudInDB_StoresAllKeys(t *testing.T) { + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) + + cfg := newCfg() + LoadNcCloudInDB(cfg, db) + + keys := []string{ + "metadata/vm1/meta-data", + "metadata/vm1/user-data", + "metadata/vm1/network-config", + "metadata/vm1/vendor-data", + "metadata/vm1/vpc", + "metadata/vm1/bind_ip", + "metadata/vm1/bind_port", } - return string(b) -} - -func TestLoadNcCloudInDB_StoresAllFiles(t *testing.T) { - dir := useTestDir(t) - LoadNcCloudInDB(newCfg(), dir) - - files := []string{"meta-data", "user-data", "network-config", "vendor-data", "vpc", "bind_ip", "bind_port"} - for _, f := range files { - path := filepath.Join(dir, "vm1", f) - if _, err := os.Stat(path); err != nil { - t.Errorf("fichier %q absent : %v", f, err) + for _, key := range keys { + val, err := kv.GetFromDB(db, key) + if err != nil { + t.Errorf("clé %q absente après LoadNcCloudInDB : %v", key, err) + } + if val == "" && key != "metadata/vm1/user-data" { + t.Errorf("clé %q vide après LoadNcCloudInDB", key) } } } func TestLoadNcCloudInDB_VpcAndBindValues(t *testing.T) { - dir := useTestDir(t) - LoadNcCloudInDB(newCfg(), dir) + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) - if vpc := readTestFile(t, dir, "vm1", "vpc"); vpc != "vpc-test" { + cfg := newCfg() + LoadNcCloudInDB(cfg, db) + + vpc, _ := kv.GetFromDB(db, "metadata/vm1/vpc") + if vpc != "vpc-test" { t.Errorf("vpc attendu %q, obtenu %q", "vpc-test", vpc) } - if ip := readTestFile(t, dir, "vm1", "bind_ip"); ip != "169.254.169.254" { + + ip, _ := kv.GetFromDB(db, "metadata/vm1/bind_ip") + if ip != "169.254.169.254" { t.Errorf("bind_ip attendu %q, obtenu %q", "169.254.169.254", ip) } - if port := readTestFile(t, dir, "vm1", "bind_port"); port != "80" { + + port, _ := kv.GetFromDB(db, "metadata/vm1/bind_port") + if port != "80" { t.Errorf("bind_port attendu %q, obtenu %q", "80", port) } } -func TestUnLoadNoCloudInDB_RemovesAllFiles(t *testing.T) { - dir := useTestDir(t) - LoadNcCloudInDB(newCfg(), dir) - UnLoadNoCloudInDB("vm1", dir) +func TestUnLoadNoCloudInDB_RemovesAllKeys(t *testing.T) { + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) - if _, err := os.Stat(filepath.Join(dir, "vm1")); !os.IsNotExist(err) { - t.Error("répertoire vm1 devrait être supprimé après UnLoadNoCloudInDB") + cfg := newCfg() + LoadNcCloudInDB(cfg, db) + UnLoadNoCloudInDB("vm1", db) + + keys := []string{ + "metadata/vm1/meta-data", + "metadata/vm1/user-data", + "metadata/vm1/network-config", + "metadata/vm1/vendor-data", + "metadata/vm1/vpc", + "metadata/vm1/bind_ip", + "metadata/vm1/bind_port", } -} - -func TestUnLoadNoCloudInDB_DoesNotAffectOtherVMs(t *testing.T) { - dir := useTestDir(t) - - cfg1 := newCfg() - cfg2 := newCfg() - cfg2.Name = "vm2" - LoadNcCloudInDB(cfg1, dir) - LoadNcCloudInDB(cfg2, dir) - - UnLoadNoCloudInDB("vm1", dir) - - if _, err := os.Stat(filepath.Join(dir, "vm2", "vpc")); err != nil { - t.Errorf("vm2 ne devrait pas être supprimée : %v", err) + for _, key := range keys { + _, err := kv.GetFromDB(db, key) + if err == nil { + t.Errorf("clé %q devrait être supprimée après UnLoadNoCloudInDB", key) + } } } @@ -264,3 +277,23 @@ func TestRootHandler_ContentType(t *testing.T) { t.Errorf("Content-Type attendu text/yaml, obtenu %q", ct) } } + +// --- UnLoadNoCloudInDB_DoesNotAffectOtherVMs --- + +func TestUnLoadNoCloudInDB_DoesNotAffectOtherVMs(t *testing.T) { + db := kv.InitDB(kv.Config{Path: t.TempDir()}, false) + t.Cleanup(func() { db.Close() }) + + cfg1 := newCfg() + cfg2 := newCfg() + cfg2.Name = "vm2" + LoadNcCloudInDB(cfg1, db) + LoadNcCloudInDB(cfg2, db) + + UnLoadNoCloudInDB("vm1", db) + + _, err := kv.GetFromDB(db, "metadata/vm2/vpc") + if err != nil { + t.Errorf("vm2 ne devrait pas être supprimée : %v", err) + } +} diff --git a/internal/metadata/render.go b/internal/metadata/render.go index 0d4fd59..f6c3cae 100644 --- a/internal/metadata/render.go +++ b/internal/metadata/render.go @@ -3,9 +3,10 @@ package metadata import ( "bytes" "embed" - "os" - "path/filepath" "text/template" + + "git.g3e.fr/syonad/two/pkg/db/kv" + "github.com/dgraph-io/badger/v4" ) //go:embed templates/*.tmpl @@ -25,25 +26,21 @@ func RenderConfig(path string, cfg NoCloudConfig) (string, error) { return buf.String(), nil } -func LoadNcCloudInDB(config NoCloudConfig, runDir string) { +func LoadNcCloudInDB(config NoCloudConfig, db *badger.DB) { meta_data, _ := RenderConfig("templates/meta-data.tmpl", config) user_data, _ := RenderConfig("templates/user-data.tmpl", config) network_config, _ := RenderConfig("templates/network-config.tmpl", config) vendor_data, _ := RenderConfig("templates/vendor-data.tmpl", config) - dir := filepath.Join(runDir, config.Name) - if err := os.MkdirAll(dir, 0755); err != nil { - return - } - os.WriteFile(filepath.Join(dir, "meta-data"), []byte(meta_data), 0644) - os.WriteFile(filepath.Join(dir, "user-data"), []byte(user_data), 0644) - os.WriteFile(filepath.Join(dir, "network-config"), []byte(network_config), 0644) - os.WriteFile(filepath.Join(dir, "vendor-data"), []byte(vendor_data), 0644) - os.WriteFile(filepath.Join(dir, "vpc"), []byte(config.VpcName), 0644) - os.WriteFile(filepath.Join(dir, "bind_ip"), []byte(config.BindIP), 0644) - os.WriteFile(filepath.Join(dir, "bind_port"), []byte(config.BindPort), 0644) + kv.AddInDB(db, "metadata/"+config.Name+"/meta-data", meta_data) + kv.AddInDB(db, "metadata/"+config.Name+"/user-data", user_data) + kv.AddInDB(db, "metadata/"+config.Name+"/network-config", network_config) + kv.AddInDB(db, "metadata/"+config.Name+"/vendor-data", vendor_data) + kv.AddInDB(db, "metadata/"+config.Name+"/vpc", config.VpcName) + kv.AddInDB(db, "metadata/"+config.Name+"/bind_ip", config.BindIP) + kv.AddInDB(db, "metadata/"+config.Name+"/bind_port", config.BindPort) } -func UnLoadNoCloudInDB(vmName string, runDir string) { - os.RemoveAll(filepath.Join(runDir, vmName)) +func UnLoadNoCloudInDB(vm_name string, db *badger.DB) { + kv.DeleteInDB(db, "metadata/"+vm_name) } diff --git a/internal/metadata/server.go b/internal/metadata/server.go index b1f8943..3b4e12f 100644 --- a/internal/metadata/server.go +++ b/internal/metadata/server.go @@ -5,13 +5,12 @@ import ( "log" "net" "net/http" - "os" - "path/filepath" "strconv" - "strings" "time" + configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/netns" + "git.g3e.fr/syonad/two/pkg/db/kv" ) var data NoCloudData @@ -24,23 +23,47 @@ func getIP(r *http.Request) string { return ip } -func readFile(dir, name string) string { - b, _ := os.ReadFile(filepath.Join(dir, name)) - return strings.TrimRight(string(b), "\n") -} +func getFromDB(config ServerConfig) NoCloudData { + var netns_name string + var port int + var iface string -func getFromFiles(config ServerConfig) NoCloudData { - dir := filepath.Join(config.RunDir, config.VmName) + conf_db, _ := configuration.LoadConfig(config.ConfFile) - port, _ := strconv.Atoi(readFile(dir, "bind_port")) + db := kv.InitDB(kv.Config{Path: conf_db.Database.Path}, true) + defer db.Close() + + metadata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/meta-data") + userdata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/user-data") + networkconfig, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/network-config") + vendordata, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/vendor-data") + + if config.Netns == "" { + netns_name, _ = kv.GetFromDB(db, "metadata/"+config.VmName+"/vpc") + } else { + netns_name = config.Netns + } + + if config.Iface == "" { + iface, _ = kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_ip") + } else { + iface = config.Iface + } + + if config.Port == 0 { + sport, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_port") + port, _ = strconv.Atoi(sport) + } else { + port = config.Port + } return NoCloudData{ - MetaData: readFile(dir, "meta-data"), - UserData: readFile(dir, "user-data"), - NetworkConfig: readFile(dir, "network-config"), - VendorData: readFile(dir, "vendor-data"), - NetNs: readFile(dir, "vpc"), - Iface: readFile(dir, "bind_ip"), + MetaData: metadata, + UserData: userdata, + NetworkConfig: networkconfig, + VendorData: vendordata, + NetNs: netns_name, + Iface: iface, Port: port, } } @@ -70,7 +93,7 @@ func rootHandler(w http.ResponseWriter, r *http.Request) { } func StartServer(config ServerConfig) { - data = getFromFiles(config) + data = getFromDB(config) if data.NetNs != "" { if err := netns.Enter(data.NetNs); err != nil { diff --git a/internal/metadata/struct.go b/internal/metadata/struct.go index e706087..5810ff4 100644 --- a/internal/metadata/struct.go +++ b/internal/metadata/struct.go @@ -11,8 +11,12 @@ type NoCloudData struct { } type ServerConfig struct { - VmName string - RunDir string + Netns string + File string + Iface string + Port int + ConfFile string + VmName string } type NoCloudConfig struct { diff --git a/internal/vm/create.go b/internal/vm/create.go index 53e2e96..4737e62 100644 --- a/internal/vm/create.go +++ b/internal/vm/create.go @@ -3,7 +3,6 @@ package vm import ( "fmt" - configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/iptables" "git.g3e.fr/syonad/two/internal/metadata" "git.g3e.fr/syonad/two/internal/netif" @@ -14,7 +13,7 @@ import ( "github.com/dgraph-io/badger/v4" ) -func StartVM(db *badger.DB, name string, cfg *configuration.Config) error { +func StartVM(db *badger.DB, name string) error { state, err := kv.GetFromDB(db, "vm/"+name+"/state") if err != nil { return err @@ -45,7 +44,7 @@ func StartVM(db *badger.DB, name string, cfg *configuration.Config) error { BindPort: d.metadataPort, Password: d.password, SSHKEY: d.sshkey, - }, cfg, false); err != nil { + }, db, false); err != nil { return fmt.Errorf("start metadata: %w", err) } diff --git a/internal/vm/delete.go b/internal/vm/delete.go index 5030339..faadb4b 100644 --- a/internal/vm/delete.go +++ b/internal/vm/delete.go @@ -57,7 +57,7 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error { return fmt.Errorf("delete metadata redirect: %w", err) } - if err := metadata.StopMetadata(name, cfg, false); err != nil { + if err := metadata.StopMetadata(name, db, false); err != nil { return fmt.Errorf("stop metadata: %w", err) } diff --git a/pkg/db/kv/init.go b/pkg/db/kv/init.go index 0ee2374..677c656 100644 --- a/pkg/db/kv/init.go +++ b/pkg/db/kv/init.go @@ -1,8 +1,6 @@ package kv import ( - "log" - "github.com/dgraph-io/badger/v4" ) @@ -17,7 +15,7 @@ func InitDB(conf Config, readonly bool) *badger.DB { opts.NumLevelZeroTablesStall = 2 db, err := badger.Open(opts) if err != nil { - log.Fatalf("kv.InitDB (readonly=%v, path=%s): %v", readonly, conf.Path, err) + panic(err) } return db }