Compare commits
7 commits
dbe7e9dd52
...
895d03d362
| Author | SHA1 | Date | |
|---|---|---|---|
|
895d03d362 |
|||
|
5ee2ba1669 |
|||
|
cd22185ddb |
|||
|
378808ba64 |
|||
|
2063199ebd |
|||
|
ebac5afcb6 |
|||
|
28f4dbf7d7 |
12 changed files with 439 additions and 118 deletions
19
internal/dhcp/db.go
Normal file
19
internal/dhcp/db.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package dhcp
|
||||
|
||||
import (
|
||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
func StoreDHCPEntries(db *badger.DB, subnetName string, entries map[string]string) error {
|
||||
for ip, mac := range entries {
|
||||
if err := kv.AddInDB(db, "subnet/"+subnetName+"/dhcp/"+ip, mac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMACForIP(db *badger.DB, subnetName, ip string) (string, error) {
|
||||
return kv.GetFromDB(db, "subnet/"+subnetName+"/dhcp/"+ip)
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ func newConf(t *testing.T, cidr string) Config {
|
|||
|
||||
func TestGenerateConfig_CreatesFile(t *testing.T) {
|
||||
conf := newConf(t, "192.168.1.0/29") // 6 hôtes
|
||||
path, err := GenerateConfig(conf)
|
||||
path, _, err := GenerateConfig(conf)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfig a échoué : %v", err)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func TestGenerateConfig_CreatesFile(t *testing.T) {
|
|||
|
||||
func TestGenerateConfig_FilenameMatchesName(t *testing.T) {
|
||||
conf := newConf(t, "192.168.1.0/29")
|
||||
path, err := GenerateConfig(conf)
|
||||
path, _, err := GenerateConfig(conf)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfig a échoué : %v", err)
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ func TestGenerateConfig_FilenameMatchesName(t *testing.T) {
|
|||
|
||||
func TestGenerateConfig_ContainsGateway(t *testing.T) {
|
||||
conf := newConf(t, "192.168.1.0/29")
|
||||
path, _ := GenerateConfig(conf)
|
||||
path, _, _ := GenerateConfig(conf)
|
||||
content, _ := os.ReadFile(path)
|
||||
|
||||
if !strings.Contains(string(content), "dhcp-option=3,192.168.1.1") {
|
||||
|
|
@ -102,7 +102,7 @@ func TestGenerateConfig_ContainsDhcpRange(t *testing.T) {
|
|||
Name: "vpc1",
|
||||
ConfDir: t.TempDir(),
|
||||
}
|
||||
path, _ := GenerateConfig(conf)
|
||||
path, _, _ := GenerateConfig(conf)
|
||||
content, _ := os.ReadFile(path)
|
||||
|
||||
if !strings.Contains(string(content), "dhcp-range=10.10.0.0,static,255.255.255.0,12h") {
|
||||
|
|
@ -113,7 +113,7 @@ func TestGenerateConfig_ContainsDhcpRange(t *testing.T) {
|
|||
func TestGenerateConfig_OneHostEntryPerIP(t *testing.T) {
|
||||
// /29 = réseau + broadcast + 6 hôtes → 8 adresses
|
||||
conf := newConf(t, "10.0.0.0/29")
|
||||
path, _ := GenerateConfig(conf)
|
||||
path, _, _ := GenerateConfig(conf)
|
||||
content, _ := os.ReadFile(path)
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
|
|
@ -131,7 +131,7 @@ func TestGenerateConfig_OneHostEntryPerIP(t *testing.T) {
|
|||
|
||||
func TestGenerateConfig_MACPrefix(t *testing.T) {
|
||||
conf := newConf(t, "10.0.0.0/30") // 4 adresses
|
||||
path, _ := GenerateConfig(conf)
|
||||
path, _, _ := GenerateConfig(conf)
|
||||
content, _ := os.ReadFile(path)
|
||||
|
||||
if !strings.Contains(string(content), "00:22:33:") {
|
||||
|
|
@ -148,7 +148,7 @@ func TestGenerateConfig_CreatesConfDir(t *testing.T) {
|
|||
Name: "net",
|
||||
ConfDir: dir,
|
||||
}
|
||||
if _, err := GenerateConfig(conf); err != nil {
|
||||
if _, _, err := GenerateConfig(conf); err != nil {
|
||||
t.Fatalf("GenerateConfig devrait créer les répertoires manquants : %v", err)
|
||||
}
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func GenerateConfig(c Config) (string, error) {
|
||||
func GenerateConfig(c Config) (string, map[string]string, error) {
|
||||
mask := fmt.Sprintf("%d.%d.%d.%d", c.Network.Mask[0], c.Network.Mask[1], c.Network.Mask[2], c.Network.Mask[3])
|
||||
|
||||
var sb strings.Builder
|
||||
|
|
@ -17,18 +17,20 @@ func GenerateConfig(c Config) (string, error) {
|
|||
fmt.Fprintf(&sb, "dhcp-option=3,%s\n", c.Gateway.String())
|
||||
fmt.Fprintf(&sb, "dhcp-option=6,1.1.1.1,8.8.8.8\n\n")
|
||||
|
||||
entries := make(map[string]string)
|
||||
i := 0
|
||||
for ip := cloneIP(c.Network.IP); c.Network.Contains(ip); incrementIP(ip) {
|
||||
fmt.Fprintf(&sb, "dhcp-host=00:22:33:%02X:%02X:%02X,%s\n",
|
||||
(i>>16)&0xFF, (i>>8)&0xFF, i&0xFF, ip)
|
||||
mac := fmt.Sprintf("00:22:33:%02X:%02X:%02X", (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF)
|
||||
fmt.Fprintf(&sb, "dhcp-host=%s,%s\n", mac, ip)
|
||||
entries[ip.String()] = mac
|
||||
i++
|
||||
}
|
||||
|
||||
outPath := filepath.Join(c.ConfDir, c.Name+".conf")
|
||||
if err := os.MkdirAll(c.ConfDir, 0755); err != nil {
|
||||
return "", err
|
||||
return "", nil, err
|
||||
}
|
||||
return outPath, os.WriteFile(outPath, []byte(sb.String()), 0644)
|
||||
return outPath, entries, os.WriteFile(outPath, []byte(sb.String()), 0644)
|
||||
}
|
||||
|
||||
func incrementIP(ip net.IP) {
|
||||
|
|
|
|||
42
internal/iptables/iptables.go
Normal file
42
internal/iptables/iptables.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func addRule(args ...string) error {
|
||||
return exec.Command("iptables", append([]string{"-t", "nat", "-A"}, args...)...).Run()
|
||||
}
|
||||
|
||||
func deleteRule(args ...string) error {
|
||||
return exec.Command("iptables", append([]string{"-t", "nat", "-D"}, args...)...).Run()
|
||||
}
|
||||
|
||||
func AddMetadataRedirect(vmIP, gatewayIP, metadataPort string) error {
|
||||
if err := addRule("PREROUTING",
|
||||
"-s", vmIP+"/32",
|
||||
"-d", "169.254.169.254/32",
|
||||
"-p", "tcp", "-m", "tcp",
|
||||
"--dport", "80",
|
||||
"-j", "DNAT",
|
||||
"--to-destination", gatewayIP+":"+metadataPort,
|
||||
); err != nil {
|
||||
return fmt.Errorf("iptables metadata redirect: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteMetadataRedirect(vmIP, gatewayIP, metadataPort string) error {
|
||||
if err := deleteRule("PREROUTING",
|
||||
"-s", vmIP+"/32",
|
||||
"-d", "169.254.169.254/32",
|
||||
"-p", "tcp", "-m", "tcp",
|
||||
"--dport", "80",
|
||||
"-j", "DNAT",
|
||||
"--to-destination", gatewayIP+":"+metadataPort,
|
||||
); err != nil {
|
||||
return fmt.Errorf("iptables delete metadata redirect: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -26,3 +26,9 @@ func CreateTap(tapID int, bridgeName, vpcName string) error {
|
|||
return LinkSetUp(name)
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteTap(tapID int, vpcName string) error {
|
||||
return netns.Call(vpcName, func() error {
|
||||
return DeleteLink(fmt.Sprintf("tap%d", tapID))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,3 +7,7 @@ import "errors"
|
|||
func CreateTap(_ int, _, _ string) error {
|
||||
return errors.New("netif: tap not supported on this platform")
|
||||
}
|
||||
|
||||
func DeleteTap(_ int, _ string) error {
|
||||
return errors.New("netif: tap not supported on this platform")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ package subnet
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/dhcp"
|
||||
"git.g3e.fr/syonad/two/internal/ebtables"
|
||||
|
|
@ -25,94 +22,51 @@ func CreateSubnet(db *badger.DB, subnetName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// lecture des paramètres depuis la DB
|
||||
vpcName, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vpc")
|
||||
d, err := loadSubnet(db, subnetName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get vpc: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
vxlanIDStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vxlan_id")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get vxlan_id: %w", err)
|
||||
}
|
||||
vxlanID, err := strconv.Atoi(vxlanIDStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse vxlan_id: %w", err)
|
||||
}
|
||||
vxlanIface := fmt.Sprintf("vxlan-%d", d.vxlanID)
|
||||
|
||||
localIface, err := kv.GetFromDB(db, "subnet/"+subnetName+"/local_iface")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get local_iface: %w", err)
|
||||
}
|
||||
|
||||
gatewayIPStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get gateway_ip: %w", err)
|
||||
}
|
||||
gatewayIP := net.ParseIP(gatewayIPStr)
|
||||
if gatewayIP == nil {
|
||||
return fmt.Errorf("invalid gateway_ip: %s", gatewayIPStr)
|
||||
}
|
||||
|
||||
cidr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/cidr")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get cidr: %w", err)
|
||||
}
|
||||
_, subnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse cidr: %w", err)
|
||||
}
|
||||
|
||||
// subnet_id = partie après le premier '-' (ex: "sn-00001" -> "00001")
|
||||
subnetID := strings.SplitN(subnetName, "-", 2)[1]
|
||||
bridge := "br-" + subnetID
|
||||
vxlanIface := fmt.Sprintf("vxlan-%d", vxlanID)
|
||||
|
||||
// veth pair
|
||||
if err := netif.CreateVethToNetns("v-"+subnetID+"-e", "v-"+subnetID+"-i", "/var/run/netns/"+vpcName, 1500); err != nil {
|
||||
if err := netif.CreateVethToNetns("v-"+d.subnetID+"-e", "v-"+d.subnetID+"-i", "/var/run/netns/"+d.vpc, 1500); err != nil {
|
||||
return fmt.Errorf("create veth: %w", err)
|
||||
}
|
||||
|
||||
// bridge dans le root netns
|
||||
if err := netif.CreateBridge(bridge, 1500); err != nil {
|
||||
if err := netif.CreateBridge(d.bridge, 1500); err != nil {
|
||||
return fmt.Errorf("create bridge: %w", err)
|
||||
}
|
||||
|
||||
// bridge dans le netns VPC
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
return netif.CreateBridge(bridge, 1500)
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
return netif.CreateBridge(d.bridge, 1500)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create bridge in netns: %w", err)
|
||||
}
|
||||
|
||||
// vxlan
|
||||
if err := netif.CreateVxlan(vxlanIface, vxlanID, localIface, 1500); err != nil {
|
||||
if err := netif.CreateVxlan(vxlanIface, d.vxlanID, d.localIface, 1500); err != nil {
|
||||
return fmt.Errorf("create vxlan: %w", err)
|
||||
}
|
||||
|
||||
// ajout des interfaces dans les bridges
|
||||
if err := netif.BridgeSetMaster("v-"+subnetID+"-e", bridge); err != nil {
|
||||
if err := netif.BridgeSetMaster("v-"+d.subnetID+"-e", d.bridge); err != nil {
|
||||
return fmt.Errorf("add veth-e to bridge: %w", err)
|
||||
}
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
return netif.BridgeSetMaster("v-"+subnetID+"-i", bridge)
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
return netif.BridgeSetMaster("v-"+d.subnetID+"-i", d.bridge)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("add veth-i to bridge in netns: %w", err)
|
||||
}
|
||||
if err := netif.BridgeSetMaster(vxlanIface, bridge); err != nil {
|
||||
if err := netif.BridgeSetMaster(vxlanIface, d.bridge); err != nil {
|
||||
return fmt.Errorf("add vxlan to bridge: %w", err)
|
||||
}
|
||||
|
||||
// montée des interfaces dans le root netns
|
||||
for _, iface := range []string{"v-" + subnetID + "-e", vxlanIface, bridge} {
|
||||
for _, iface := range []string{"v-" + d.subnetID + "-e", vxlanIface, d.bridge} {
|
||||
if err := netif.LinkSetUp(iface); err != nil {
|
||||
return fmt.Errorf("set up %s: %w", iface, err)
|
||||
}
|
||||
}
|
||||
|
||||
// montée des interfaces dans le netns VPC
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
for _, iface := range []string{"v-" + subnetID + "-i", bridge} {
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
for _, iface := range []string{"v-" + d.subnetID + "-i", d.bridge} {
|
||||
if err := netif.LinkSetUp(iface); err != nil {
|
||||
return fmt.Errorf("set up %s: %w", iface, err)
|
||||
}
|
||||
|
|
@ -122,37 +76,38 @@ func CreateSubnet(db *badger.DB, subnetName string) error {
|
|||
return fmt.Errorf("set up interfaces in netns: %w", err)
|
||||
}
|
||||
|
||||
// IP gateway (/32) sur le bridge interne
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
return netif.AddrAdd(bridge, gatewayIP)
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
return netif.AddrAdd(d.bridge, d.gatewayIP)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("add addr to bridge in netns: %w", err)
|
||||
}
|
||||
|
||||
// route subnet (scope link) dans le netns VPC
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
return netif.RouteAdd(bridge, subnet)
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
return netif.RouteAdd(d.bridge, d.cidr)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("add route in netns: %w", err)
|
||||
}
|
||||
|
||||
if err := ebtables.DropARPToGateway(bridge, gatewayIP.String()); err != nil {
|
||||
if err := ebtables.DropARPToGateway(d.bridge, d.gatewayIP.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ebtables.DropDHCP(bridge); err != nil {
|
||||
if err := ebtables.DropDHCP(d.bridge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// génération de la config dnsmasq et démarrage du service
|
||||
conf := dhcp.Config{
|
||||
Network: subnet,
|
||||
Gateway: gatewayIP,
|
||||
Name: vpcName + "_" + bridge,
|
||||
Network: d.cidr,
|
||||
Gateway: d.gatewayIP,
|
||||
Name: d.vpc + "_" + d.bridge,
|
||||
ConfDir: "/etc/dnsmasq.d",
|
||||
}
|
||||
if _, err := dhcp.GenerateConfig(conf); err != nil {
|
||||
_, entries, err := dhcp.GenerateConfig(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate dhcp config: %w", err)
|
||||
}
|
||||
if err := dhcp.StoreDHCPEntries(db, subnetName, entries); err != nil {
|
||||
return fmt.Errorf("store dhcp entries: %w", err)
|
||||
}
|
||||
|
||||
svc, err := systemd.New()
|
||||
if err != nil {
|
||||
|
|
|
|||
72
internal/subnet/data.go
Normal file
72
internal/subnet/data.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package subnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
type subnetData struct {
|
||||
vpc string
|
||||
subnetID string
|
||||
bridge string
|
||||
vxlanID int
|
||||
localIface string
|
||||
gatewayIP net.IP
|
||||
cidr *net.IPNet
|
||||
}
|
||||
|
||||
func loadSubnet(db *badger.DB, name string) (subnetData, error) {
|
||||
var d subnetData
|
||||
|
||||
d.subnetID = strings.SplitN(name, "-", 2)[1]
|
||||
d.bridge = "br-" + d.subnetID
|
||||
|
||||
vpc, err := kv.GetFromDB(db, "subnet/"+name+"/vpc")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get vpc: %w", err)
|
||||
}
|
||||
d.vpc = vpc
|
||||
|
||||
vxlanIDStr, err := kv.GetFromDB(db, "subnet/"+name+"/vxlan_id")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get vxlan_id: %w", err)
|
||||
}
|
||||
vxlanID, err := strconv.Atoi(vxlanIDStr)
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("parse vxlan_id: %w", err)
|
||||
}
|
||||
d.vxlanID = vxlanID
|
||||
|
||||
localIface, err := kv.GetFromDB(db, "subnet/"+name+"/local_iface")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get local_iface: %w", err)
|
||||
}
|
||||
d.localIface = localIface
|
||||
|
||||
gatewayIPStr, err := kv.GetFromDB(db, "subnet/"+name+"/gateway_ip")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get gateway_ip: %w", err)
|
||||
}
|
||||
gatewayIP := net.ParseIP(gatewayIPStr)
|
||||
if gatewayIP == nil {
|
||||
return d, fmt.Errorf("invalid gateway_ip: %s", gatewayIPStr)
|
||||
}
|
||||
d.gatewayIP = gatewayIP
|
||||
|
||||
cidrStr, err := kv.GetFromDB(db, "subnet/"+name+"/cidr")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get cidr: %w", err)
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("parse cidr: %w", err)
|
||||
}
|
||||
d.cidr = ipNet
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package subnet
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/ebtables"
|
||||
"git.g3e.fr/syonad/two/internal/netif"
|
||||
|
|
@ -23,69 +22,52 @@ func DeleteSubnet(db *badger.DB, subnetName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
vpcName, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vpc")
|
||||
d, err := loadSubnet(db, subnetName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get vpc: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
vxlanIDStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vxlan_id")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get vxlan_id: %w", err)
|
||||
}
|
||||
vxlanIface := fmt.Sprintf("vxlan-%d", d.vxlanID)
|
||||
|
||||
gatewayIP, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get gateway_ip: %w", err)
|
||||
}
|
||||
|
||||
subnetID := strings.SplitN(subnetName, "-", 2)[1]
|
||||
bridge := "br-" + subnetID
|
||||
vxlanIface := "vxlan-" + vxlanIDStr
|
||||
|
||||
// arrêt du service dnsmasq
|
||||
svc, err := systemd.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to systemd: %w", err)
|
||||
}
|
||||
defer svc.Close()
|
||||
|
||||
svcName := "dnsmasq@" + vpcName + "_" + bridge + ".service"
|
||||
if err := svc.Stop(svcName); err != nil {
|
||||
if err := svc.Stop("dnsmasq@" + d.vpc + "_" + d.bridge + ".service"); err != nil {
|
||||
return fmt.Errorf("stop dnsmasq: %w", err)
|
||||
}
|
||||
|
||||
// suppression de la config dnsmasq
|
||||
if err := os.Remove("/etc/dnsmasq.d/" + vpcName + "_" + bridge + ".conf"); err != nil && !os.IsNotExist(err) {
|
||||
if err := os.Remove("/etc/dnsmasq.d/" + d.vpc + "_" + d.bridge + ".conf"); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("remove dnsmasq config: %w", err)
|
||||
}
|
||||
if err := kv.DeleteInDB(db, "subnet/"+subnetName+"/dhcp"); err != nil {
|
||||
return fmt.Errorf("delete dhcp entries: %w", err)
|
||||
}
|
||||
|
||||
// suppression des règles ebtables
|
||||
if err := ebtables.DeleteARPToGateway(bridge, gatewayIP); err != nil {
|
||||
if err := ebtables.DeleteARPToGateway(d.bridge, d.gatewayIP.String()); err != nil {
|
||||
return fmt.Errorf("delete ebtables arp rule: %w", err)
|
||||
}
|
||||
if err := ebtables.DeleteDHCP(bridge); err != nil {
|
||||
if err := ebtables.DeleteDHCP(d.bridge); err != nil {
|
||||
return fmt.Errorf("delete ebtables dhcp rule: %w", err)
|
||||
}
|
||||
|
||||
// suppression du bridge dans le netns VPC
|
||||
if err := netns.Call(vpcName, func() error {
|
||||
return netif.DeleteLink(bridge)
|
||||
if err := netns.Call(d.vpc, func() error {
|
||||
return netif.DeleteLink(d.bridge)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("delete bridge in netns: %w", err)
|
||||
}
|
||||
|
||||
// suppression du vxlan
|
||||
if err := netif.DeleteLink(vxlanIface); err != nil {
|
||||
return fmt.Errorf("delete vxlan: %w", err)
|
||||
}
|
||||
|
||||
// suppression du veth pair (supprime les deux côtés)
|
||||
if err := netif.DeleteLink("v-" + subnetID + "-e"); err != nil {
|
||||
if err := netif.DeleteLink("v-" + d.subnetID + "-e"); err != nil {
|
||||
return fmt.Errorf("delete veth: %w", err)
|
||||
}
|
||||
|
||||
// suppression du bridge dans le root netns
|
||||
if err := netif.DeleteLink(bridge); err != nil {
|
||||
if err := netif.DeleteLink(d.bridge); err != nil {
|
||||
return fmt.Errorf("delete bridge: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
64
internal/vm/create.go
Normal file
64
internal/vm/create.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/iptables"
|
||||
"git.g3e.fr/syonad/two/internal/metadata"
|
||||
"git.g3e.fr/syonad/two/internal/netif"
|
||||
"git.g3e.fr/syonad/two/internal/netns"
|
||||
"git.g3e.fr/syonad/two/internal/qemu"
|
||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
func StartVM(db *badger.DB, name string) error {
|
||||
state, err := kv.GetFromDB(db, "vm/"+name+"/state")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state != "starting" {
|
||||
return nil
|
||||
}
|
||||
|
||||
d, err := loadVM(db, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := netif.CreateTap(d.tapID, d.bridge, d.vpcName); err != nil {
|
||||
return fmt.Errorf("create tap: %w", err)
|
||||
}
|
||||
|
||||
if err := netns.Call(d.vpcName, func() error {
|
||||
return iptables.AddMetadataRedirect(d.ip, d.gatewayIP, d.metadataPort)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("add metadata redirect: %w", err)
|
||||
}
|
||||
|
||||
if err := metadata.StartMetadata(metadata.NoCloudConfig{
|
||||
Name: name,
|
||||
VpcName: d.vpcName,
|
||||
BindIP: d.gatewayIP,
|
||||
BindPort: d.metadataPort,
|
||||
Password: d.password,
|
||||
SSHKEY: d.sshkey,
|
||||
}, db, false); err != nil {
|
||||
return fmt.Errorf("start metadata: %w", err)
|
||||
}
|
||||
|
||||
if err := qemu.Start(qemu.Config{
|
||||
Name: name,
|
||||
VpcName: d.vpcName,
|
||||
TapID: d.tapID,
|
||||
Mac: d.mac,
|
||||
VolumePath: d.volumePath,
|
||||
Memory: d.memory,
|
||||
CPUs: d.cpus,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("start qemu: %w", err)
|
||||
}
|
||||
|
||||
return kv.AddInDB(db, "vm/"+name+"/state", "started")
|
||||
}
|
||||
106
internal/vm/data.go
Normal file
106
internal/vm/data.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/dhcp"
|
||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
type vmData struct {
|
||||
subnetName string
|
||||
vpcName string
|
||||
gatewayIP string
|
||||
bridge string
|
||||
tapID int
|
||||
ip string
|
||||
metadataPort string
|
||||
mac string
|
||||
volumePath string
|
||||
memory int
|
||||
cpus int
|
||||
password string
|
||||
sshkey string
|
||||
}
|
||||
|
||||
func loadVM(db *badger.DB, name string) (vmData, error) {
|
||||
var d vmData
|
||||
|
||||
subnetName, err := kv.GetFromDB(db, "vm/"+name+"/subnet")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get subnet: %w", err)
|
||||
}
|
||||
d.subnetName = subnetName
|
||||
d.bridge = "br-" + strings.SplitN(subnetName, "-", 2)[1]
|
||||
|
||||
vpcName, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vpc")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get vpc: %w", err)
|
||||
}
|
||||
d.vpcName = vpcName
|
||||
|
||||
gatewayIP, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get gateway_ip: %w", err)
|
||||
}
|
||||
d.gatewayIP = gatewayIP
|
||||
|
||||
tapIDStr, err := kv.GetFromDB(db, "vm/"+name+"/tap_id")
|
||||
if err != nil {
|
||||
d.tapID = rand.Intn(90000000) + 10000000
|
||||
if err := kv.AddInDB(db, "vm/"+name+"/tap_id", strconv.Itoa(d.tapID)); err != nil {
|
||||
return d, fmt.Errorf("store tap_id: %w", err)
|
||||
}
|
||||
} else {
|
||||
tapID, err := strconv.Atoi(tapIDStr)
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("parse tap_id: %w", err)
|
||||
}
|
||||
d.tapID = tapID
|
||||
}
|
||||
|
||||
ip, err := kv.GetFromDB(db, "vm/"+name+"/ip")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get ip: %w", err)
|
||||
}
|
||||
d.ip = ip
|
||||
|
||||
metadataPort, err := kv.GetFromDB(db, "vm/"+name+"/metadata_port")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get metadata_port: %w", err)
|
||||
}
|
||||
d.metadataPort = metadataPort
|
||||
|
||||
mac, err := dhcp.GetMACForIP(db, d.subnetName, d.ip)
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get mac for ip %s: %w", d.ip, err)
|
||||
}
|
||||
d.mac = mac
|
||||
|
||||
volumePath, err := kv.GetFromDB(db, "vm/"+name+"/volume_path")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get volume_path: %w", err)
|
||||
}
|
||||
d.volumePath = volumePath
|
||||
|
||||
memoryStr, err := kv.GetFromDB(db, "vm/"+name+"/memory")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get memory: %w", err)
|
||||
}
|
||||
d.memory, _ = strconv.Atoi(memoryStr)
|
||||
|
||||
cpusStr, err := kv.GetFromDB(db, "vm/"+name+"/cpus")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get cpus: %w", err)
|
||||
}
|
||||
d.cpus, _ = strconv.Atoi(cpusStr)
|
||||
|
||||
d.password, _ = kv.GetFromDB(db, "vm/"+name+"/password")
|
||||
d.sshkey, _ = kv.GetFromDB(db, "vm/"+name+"/sshkey")
|
||||
|
||||
return d, nil
|
||||
}
|
||||
69
internal/vm/delete.go
Normal file
69
internal/vm/delete.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"git.g3e.fr/syonad/two/internal/netns"
|
||||
"git.g3e.fr/syonad/two/internal/qmp"
|
||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
func StopVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
||||
state, err := kv.GetFromDB(db, "vm/"+name+"/state")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state != "stopping" {
|
||||
return nil
|
||||
}
|
||||
|
||||
d, err := loadVM(db, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socketPath := fmt.Sprintf("/tmp/%s.qmp-sock", name)
|
||||
|
||||
if _, err := qmp.Send(socketPath, []string{`{"execute":"system_powerdown"}`}); err != nil {
|
||||
return fmt.Errorf("qmp system_powerdown: %w", err)
|
||||
}
|
||||
|
||||
// attendre l'arrêt effectif de la VM ; forcer via quit après timeout
|
||||
timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second)
|
||||
poll := time.Duration(cfg.Dispatcher.PollSeconds) * time.Second
|
||||
stopped := false
|
||||
for !stopped {
|
||||
select {
|
||||
case <-timeout:
|
||||
qmp.Send(socketPath, []string{`{"execute":"quit"}`})
|
||||
stopped = true
|
||||
case <-time.After(poll):
|
||||
if _, err := qmp.Send(socketPath, nil); err != nil {
|
||||
stopped = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := netns.Call(d.vpcName, func() error {
|
||||
return iptables.DeleteMetadataRedirect(d.ip, d.gatewayIP, d.metadataPort)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("delete metadata redirect: %w", err)
|
||||
}
|
||||
|
||||
if err := metadata.StopMetadata(name, db, false); err != nil {
|
||||
return fmt.Errorf("stop metadata: %w", err)
|
||||
}
|
||||
|
||||
if err := netif.DeleteTap(d.tapID, d.vpcName); err != nil {
|
||||
return fmt.Errorf("delete tap: %w", err)
|
||||
}
|
||||
|
||||
return kv.AddInDB(db, "vm/"+name+"/state", "stopped")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue