Compare commits

..

No commits in common. "87808312f46dfd32a8d9b3bde0c56af55f817f19" and "7078691db6a6b41eee891a9b775c5036082e0dc3" have entirely different histories.

14 changed files with 217 additions and 119 deletions

View file

@ -33,7 +33,9 @@ jobs:
goos: [linux] goos: [linux]
goarch: [amd64] goarch: [amd64]
binaries: binaries:
- db
- metadata - metadata
- metacli
- agent - agent
uses: ./.forgejo/workflows/build.yml uses: ./.forgejo/workflows/build.yml
with: with:

53
cmd/metacli/main.go Normal file
View file

@ -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)
}
}
}

View file

@ -2,29 +2,26 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/metadata" "git.g3e.fr/syonad/two/internal/metadata"
) )
var ( var (
confFile = flag.String("conf", "/etc/two/agent.yml", "configuration file") 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") vm_name = flag.String("vm", "", "Name of the vm")
) )
func main() { func main() {
flag.Parse() 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{ metadata.StartServer(metadata.ServerConfig{
Netns: *netns_name,
Iface: *iface,
Port: *port,
ConfFile: *conf_file,
VmName: *vm_name, VmName: *vm_name,
RunDir: cfg.Metadata.RunDir,
}) })
} }

View file

@ -35,10 +35,6 @@ interfaces:
internet: br-000000 internet: br-000000
admin: br-000000 admin: br-000000
# Metadata server runtime directory (cloud-init files per VM)
metadata:
run_dir: "/run/two/metadata"
# Logging configuration # Logging configuration
logger: logger:
# Log level: debug, info, warn, error (default: info) # Log level: debug, info, warn, error (default: info)

View file

@ -28,9 +28,6 @@ type Config struct {
Level string `mapstructure:"level"` Level string `mapstructure:"level"`
Debug bool `mapstructure:"debug"` Debug bool `mapstructure:"debug"`
} `mapstructure:"logger"` } `mapstructure:"logger"`
Metadata struct {
RunDir string `mapstructure:"run_dir"`
} `mapstructure:"metadata"`
DefaultInterface string `mapstructure:"default_interface"` DefaultInterface string `mapstructure:"default_interface"`
Interfaces map[string]string `mapstructure:"interfaces"` Interfaces map[string]string `mapstructure:"interfaces"`
} }
@ -49,7 +46,6 @@ func LoadConfig(path string) (*Config, error) {
v.SetDefault("worker.buffer_size", 100) v.SetDefault("worker.buffer_size", 100)
v.SetDefault("dispatcher.timeout_seconds", 300) v.SetDefault("dispatcher.timeout_seconds", 300)
v.SetDefault("dispatcher.poll_seconds", 2) v.SetDefault("dispatcher.poll_seconds", 2)
v.SetDefault("metadata.run_dir", "/run/two/metadata")
v.SetDefault("default_interface", "br-000000") v.SetDefault("default_interface", "br-000000")
v.SetDefault("logger.level", "info") v.SetDefault("logger.level", "info")
v.SetDefault("logger.debug", false) v.SetDefault("logger.debug", false)

View file

@ -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): 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 { type StopVMCommand struct {

View file

@ -3,18 +3,18 @@ package metadata
import ( import (
"fmt" "fmt"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/pkg/systemd" "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() service, err := systemd.New()
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to systemd: %w", err) return fmt.Errorf("failed to connect to systemd: %w", err)
} }
defer service.Close() defer service.Close()
LoadNcCloudInDB(config, cfg.Metadata.RunDir) LoadNcCloudInDB(config, db)
if !dryrun { if !dryrun {
if err := service.Start("metadata@" + config.Name + ".service"); err != nil { if err := service.Start("metadata@" + config.Name + ".service"); err != nil {
return fmt.Errorf("failed to start metadata@%s: %w", config.Name, err) 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 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() service, err := systemd.New()
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to systemd: %w", err) return fmt.Errorf("failed to connect to systemd: %w", err)
} }
defer service.Close() defer service.Close()
UnLoadNoCloudInDB(vmName, cfg.Metadata.RunDir) UnLoadNoCloudInDB(vm_name, db)
if !dryrun { if !dryrun {
if err := service.Stop("metadata@" + vmName + ".service"); err != nil { if err := service.Stop("metadata@" + vm_name + ".service"); err != nil {
return fmt.Errorf("failed to stop metadata@%s: %w", vmName, err) return fmt.Errorf("failed to stop metadata@%s: %w", vm_name, err)
} }
} }
return nil return nil

View file

@ -3,10 +3,10 @@ package metadata
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
"git.g3e.fr/syonad/two/pkg/db/kv"
) )
func newCfg() NoCloudConfig { 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() t.Helper()
return t.TempDir() db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
return db
} }
// --- RenderConfig --- // --- RenderConfig ---
@ -106,67 +108,78 @@ func TestRenderConfig_SpecialCharsInName(t *testing.T) {
// --- LoadNcCloudInDB / UnLoadNoCloudInDB --- // --- LoadNcCloudInDB / UnLoadNoCloudInDB ---
func readTestFile(t *testing.T, dir, vmName, name string) string { func TestLoadNcCloudInDB_StoresAllKeys(t *testing.T) {
t.Helper() db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
b, err := os.ReadFile(filepath.Join(dir, vmName, name)) 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",
}
for _, key := range keys {
val, err := kv.GetFromDB(db, key)
if err != nil { if err != nil {
t.Errorf("fichier %q absent après LoadNcCloudInDB : %v", name, err) t.Errorf("clé %q absente après LoadNcCloudInDB : %v", key, err)
return ""
} }
return string(b) if val == "" && key != "metadata/vm1/user-data" {
} t.Errorf("clé %q vide après LoadNcCloudInDB", key)
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)
} }
} }
} }
func TestLoadNcCloudInDB_VpcAndBindValues(t *testing.T) { func TestLoadNcCloudInDB_VpcAndBindValues(t *testing.T) {
dir := useTestDir(t) db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
LoadNcCloudInDB(newCfg(), dir) 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) 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) 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) t.Errorf("bind_port attendu %q, obtenu %q", "80", port)
} }
} }
func TestUnLoadNoCloudInDB_RemovesAllFiles(t *testing.T) { func TestUnLoadNoCloudInDB_RemovesAllKeys(t *testing.T) {
dir := useTestDir(t) db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
LoadNcCloudInDB(newCfg(), dir) t.Cleanup(func() { db.Close() })
UnLoadNoCloudInDB("vm1", dir)
if _, err := os.Stat(filepath.Join(dir, "vm1")); !os.IsNotExist(err) { cfg := newCfg()
t.Error("répertoire vm1 devrait être supprimé après UnLoadNoCloudInDB") 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",
} }
for _, key := range keys {
_, err := kv.GetFromDB(db, key)
if err == nil {
t.Errorf("clé %q devrait être supprimée après UnLoadNoCloudInDB", key)
} }
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)
} }
} }
@ -264,3 +277,23 @@ func TestRootHandler_ContentType(t *testing.T) {
t.Errorf("Content-Type attendu text/yaml, obtenu %q", ct) 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)
}
}

View file

@ -3,9 +3,10 @@ package metadata
import ( import (
"bytes" "bytes"
"embed" "embed"
"os"
"path/filepath"
"text/template" "text/template"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
) )
//go:embed templates/*.tmpl //go:embed templates/*.tmpl
@ -25,25 +26,21 @@ func RenderConfig(path string, cfg NoCloudConfig) (string, error) {
return buf.String(), nil 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) meta_data, _ := RenderConfig("templates/meta-data.tmpl", config)
user_data, _ := RenderConfig("templates/user-data.tmpl", config) user_data, _ := RenderConfig("templates/user-data.tmpl", config)
network_config, _ := RenderConfig("templates/network-config.tmpl", config) network_config, _ := RenderConfig("templates/network-config.tmpl", config)
vendor_data, _ := RenderConfig("templates/vendor-data.tmpl", config) vendor_data, _ := RenderConfig("templates/vendor-data.tmpl", config)
dir := filepath.Join(runDir, config.Name) kv.AddInDB(db, "metadata/"+config.Name+"/meta-data", meta_data)
if err := os.MkdirAll(dir, 0755); err != nil { kv.AddInDB(db, "metadata/"+config.Name+"/user-data", user_data)
return kv.AddInDB(db, "metadata/"+config.Name+"/network-config", network_config)
} kv.AddInDB(db, "metadata/"+config.Name+"/vendor-data", vendor_data)
os.WriteFile(filepath.Join(dir, "meta-data"), []byte(meta_data), 0644) kv.AddInDB(db, "metadata/"+config.Name+"/vpc", config.VpcName)
os.WriteFile(filepath.Join(dir, "user-data"), []byte(user_data), 0644) kv.AddInDB(db, "metadata/"+config.Name+"/bind_ip", config.BindIP)
os.WriteFile(filepath.Join(dir, "network-config"), []byte(network_config), 0644) kv.AddInDB(db, "metadata/"+config.Name+"/bind_port", config.BindPort)
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)
} }
func UnLoadNoCloudInDB(vmName string, runDir string) { func UnLoadNoCloudInDB(vm_name string, db *badger.DB) {
os.RemoveAll(filepath.Join(runDir, vmName)) kv.DeleteInDB(db, "metadata/"+vm_name)
} }

View file

@ -5,13 +5,12 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"path/filepath"
"strconv" "strconv"
"strings"
"time" "time"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/netns" "git.g3e.fr/syonad/two/internal/netns"
"git.g3e.fr/syonad/two/pkg/db/kv"
) )
var data NoCloudData var data NoCloudData
@ -24,23 +23,47 @@ func getIP(r *http.Request) string {
return ip return ip
} }
func readFile(dir, name string) string { func getFromDB(config ServerConfig) NoCloudData {
b, _ := os.ReadFile(filepath.Join(dir, name)) var netns_name string
return strings.TrimRight(string(b), "\n") var port int
var iface string
conf_db, _ := configuration.LoadConfig(config.ConfFile)
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
} }
func getFromFiles(config ServerConfig) NoCloudData { if config.Iface == "" {
dir := filepath.Join(config.RunDir, config.VmName) iface, _ = kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_ip")
} else {
iface = config.Iface
}
port, _ := strconv.Atoi(readFile(dir, "bind_port")) if config.Port == 0 {
sport, _ := kv.GetFromDB(db, "metadata/"+config.VmName+"/bind_port")
port, _ = strconv.Atoi(sport)
} else {
port = config.Port
}
return NoCloudData{ return NoCloudData{
MetaData: readFile(dir, "meta-data"), MetaData: metadata,
UserData: readFile(dir, "user-data"), UserData: userdata,
NetworkConfig: readFile(dir, "network-config"), NetworkConfig: networkconfig,
VendorData: readFile(dir, "vendor-data"), VendorData: vendordata,
NetNs: readFile(dir, "vpc"), NetNs: netns_name,
Iface: readFile(dir, "bind_ip"), Iface: iface,
Port: port, Port: port,
} }
} }
@ -70,7 +93,7 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
} }
func StartServer(config ServerConfig) { func StartServer(config ServerConfig) {
data = getFromFiles(config) data = getFromDB(config)
if data.NetNs != "" { if data.NetNs != "" {
if err := netns.Enter(data.NetNs); err != nil { if err := netns.Enter(data.NetNs); err != nil {

View file

@ -11,8 +11,12 @@ type NoCloudData struct {
} }
type ServerConfig struct { type ServerConfig struct {
Netns string
File string
Iface string
Port int
ConfFile string
VmName string VmName string
RunDir string
} }
type NoCloudConfig struct { type NoCloudConfig struct {

View file

@ -3,7 +3,6 @@ package vm
import ( import (
"fmt" "fmt"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
"git.g3e.fr/syonad/two/internal/iptables" "git.g3e.fr/syonad/two/internal/iptables"
"git.g3e.fr/syonad/two/internal/metadata" "git.g3e.fr/syonad/two/internal/metadata"
"git.g3e.fr/syonad/two/internal/netif" "git.g3e.fr/syonad/two/internal/netif"
@ -14,7 +13,7 @@ import (
"github.com/dgraph-io/badger/v4" "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") state, err := kv.GetFromDB(db, "vm/"+name+"/state")
if err != nil { if err != nil {
return err return err
@ -45,7 +44,7 @@ func StartVM(db *badger.DB, name string, cfg *configuration.Config) error {
BindPort: d.metadataPort, BindPort: d.metadataPort,
Password: d.password, Password: d.password,
SSHKEY: d.sshkey, SSHKEY: d.sshkey,
}, cfg, false); err != nil { }, db, false); err != nil {
return fmt.Errorf("start metadata: %w", err) return fmt.Errorf("start metadata: %w", err)
} }

View file

@ -57,7 +57,7 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error {
return fmt.Errorf("delete metadata redirect: %w", err) 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) return fmt.Errorf("stop metadata: %w", err)
} }

View file

@ -1,8 +1,6 @@
package kv package kv
import ( import (
"log"
"github.com/dgraph-io/badger/v4" "github.com/dgraph-io/badger/v4"
) )
@ -17,7 +15,7 @@ func InitDB(conf Config, readonly bool) *badger.DB {
opts.NumLevelZeroTablesStall = 2 opts.NumLevelZeroTablesStall = 2
db, err := badger.Open(opts) db, err := badger.Open(opts)
if err != nil { if err != nil {
log.Fatalf("kv.InitDB (readonly=%v, path=%s): %v", readonly, conf.Path, err) panic(err)
} }
return db return db
} }