diff --git a/api/agent.yaml b/api/agent.yaml index be328d0..1d3a77f 100644 --- a/api/agent.yaml +++ b/api/agent.yaml @@ -127,6 +127,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Subnet" + "400": + description: Missing required field or unknown iface_type + content: + application/json: + schema: + $ref: "#/components/schemas/Error" "409": description: Subnet already exists content: @@ -203,15 +209,16 @@ components: properties: name: type: string - description: Unique name for the VPC - example: vpc1 + description: Unique name for the VPC, must follow the format vp-[id] + pattern: '^vp-.+' + example: vp-00001 VPC: type: object properties: name: type: string - example: vpc1 + example: vp-00001 state: type: string enum: [creating, created, deleting, deleted] @@ -219,7 +226,7 @@ components: SubnetCreateRequest: type: object - required: [name, vpc, vxlan_id, local_ip, gateway_ip, cidr] + required: [name, vpc, vxlan_id, gateway_ip, cidr] properties: name: type: string @@ -233,11 +240,10 @@ components: type: integer description: VXLAN VNI identifier example: 100 - local_ip: + iface_type: type: string - format: ipv4 - description: Local VTEP IP address - example: "10.0.0.5" + description: Interface type key defined in the agent config (e.g. vms, internet, admin). Falls back to default_interface if omitted or unknown. + example: vms gateway_ip: type: string format: ipv4 @@ -264,9 +270,10 @@ components: vxlan_id: type: integer example: 100 - local_ip: + local_iface: type: string - example: "10.0.0.5" + description: Resolved interface name + example: br-000000 gateway_ip: type: string example: "10.10.10.1" diff --git a/cmd/agent/main.go b/cmd/agent/main.go index b61bb6f..dd6fd6d 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -36,7 +36,7 @@ func main() { apiAddr := fmt.Sprintf("%s:%d", cfg.Api.Address, cfg.Api.Port) promAddr := fmt.Sprintf("%s:%d", cfg.Prometheus.Address, cfg.Prometheus.Port) - d := dispatcher.New(q, db) + d := dispatcher.New(q, db, cfg) go agentapi.New(d, db).Start(apiAddr) go promserver.Start(promAddr, registry) diff --git a/conf/agent/config.exemple.yml b/conf/agent/config.exemple.yml index a2b9f1b..bc1d437 100644 --- a/conf/agent/config.exemple.yml +++ b/conf/agent/config.exemple.yml @@ -1,2 +1,9 @@ database: - path: "/var/lib/two/data/" \ No newline at end of file + path: "/var/lib/two/data/" + +default_interface: br-000000 + +interfaces: + vms: br-000000 + internet: br-000000 + admin: br-000000 diff --git a/internal/api/agent/models.go b/internal/api/agent/models.go index bd09c76..e826082 100644 --- a/internal/api/agent/models.go +++ b/internal/api/agent/models.go @@ -13,19 +13,19 @@ type SubnetCreateRequest struct { Name string `json:"name"` VPC string `json:"vpc"` VxlanID int `json:"vxlan_id"` - LocalIP string `json:"local_ip"` + IfaceType string `json:"iface_type"` GatewayIP string `json:"gateway_ip"` CIDR string `json:"cidr"` } type Subnet struct { - Name string `json:"name"` - State string `json:"state"` - VPC string `json:"vpc"` - VxlanID int `json:"vxlan_id"` - LocalIP string `json:"local_ip"` - GatewayIP string `json:"gateway_ip"` - CIDR string `json:"cidr"` + Name string `json:"name"` + State string `json:"state"` + VPC string `json:"vpc"` + VxlanID int `json:"vxlan_id"` + LocalIface string `json:"local_iface"` + GatewayIP string `json:"gateway_ip"` + CIDR string `json:"cidr"` } type ErrorResponse struct { diff --git a/internal/api/agent/subnets.go b/internal/api/agent/subnets.go index b251c3c..263c9ce 100644 --- a/internal/api/agent/subnets.go +++ b/internal/api/agent/subnets.go @@ -31,16 +31,16 @@ func (s *Server) postSubnet(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid request body"}) return } - if req.Name == "" || req.VPC == "" || req.LocalIP == "" || req.GatewayIP == "" || req.CIDR == "" { + if req.Name == "" || req.VPC == "" || req.IfaceType == "" || req.GatewayIP == "" || req.CIDR == "" { w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(ErrorResponse{Error: "name, vpc, local_ip, gateway_ip and cidr are required"}) + json.NewEncoder(w).Encode(ErrorResponse{Error: "name, vpc, iface_type, gateway_ip and cidr are required"}) return } s.dispatcher.Dispatch(dispatcher.CreateSubnetCommand{ Name: req.Name, VPC: req.VPC, VxlanID: req.VxlanID, - LocalIP: req.LocalIP, + IfaceType: req.IfaceType, GatewayIP: req.GatewayIP, CIDR: req.CIDR, }) @@ -50,9 +50,7 @@ func (s *Server) postSubnet(w http.ResponseWriter, r *http.Request) { State: "creating", VPC: req.VPC, VxlanID: req.VxlanID, - LocalIP: req.LocalIP, GatewayIP: req.GatewayIP, CIDR: req.CIDR, }) } - diff --git a/internal/config/agent/struct.go b/internal/config/agent/struct.go index 24ad0f5..1c9fc9f 100644 --- a/internal/config/agent/struct.go +++ b/internal/config/agent/struct.go @@ -20,6 +20,8 @@ type Config struct { Count int `mapstructure:"count"` BufferSize int `mapstructure:"buffer_size"` } `mapstructure:"worker"` + DefaultInterface string `mapstructure:"default_interface"` + Interfaces map[string]string `mapstructure:"interfaces"` } func LoadConfig(path string) (*Config, error) { @@ -34,6 +36,7 @@ func LoadConfig(path string) (*Config, error) { v.SetDefault("prometheus.port", 9090) v.SetDefault("worker.count", 4) v.SetDefault("worker.buffer_size", 100) + v.SetDefault("default_interface", "br-000000") v.ReadInConfig() diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index 937adf2..8e94827 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -3,26 +3,28 @@ package dispatcher import ( "log" + configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/pkg/worker" "github.com/dgraph-io/badger/v4" ) type Command interface { - Execute(db *badger.DB) error + Execute(db *badger.DB, cfg *configuration.Config) error } type Dispatcher struct { queue *worker.Queue db *badger.DB + cfg *configuration.Config } -func New(queue *worker.Queue, db *badger.DB) *Dispatcher { - return &Dispatcher{queue: queue, db: db} +func New(queue *worker.Queue, db *badger.DB, cfg *configuration.Config) *Dispatcher { + return &Dispatcher{queue: queue, db: db, cfg: cfg} } func (d *Dispatcher) Dispatch(cmd Command) { d.queue.Submit(func() { - if err := cmd.Execute(d.db); err != nil { + if err := cmd.Execute(d.db, d.cfg); err != nil { log.Printf("command error (%T): %v", cmd, err) } }) diff --git a/internal/dispatcher/subnet_commands.go b/internal/dispatcher/subnet_commands.go index b23949e..0d471c3 100644 --- a/internal/dispatcher/subnet_commands.go +++ b/internal/dispatcher/subnet_commands.go @@ -5,6 +5,7 @@ import ( "os" "strconv" + configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/subnet" "git.g3e.fr/syonad/two/pkg/db/kv" "github.com/dgraph-io/badger/v4" @@ -14,16 +15,20 @@ type CreateSubnetCommand struct { Name string VPC string VxlanID int - LocalIP string + IfaceType string GatewayIP string CIDR string } -func (c CreateSubnetCommand) Execute(db *badger.DB) error { +func (c CreateSubnetCommand) Execute(db *badger.DB, cfg *configuration.Config) error { + localIface, ok := cfg.Interfaces[c.IfaceType] + if !ok { + localIface = cfg.DefaultInterface + } kv.AddInDB(db, "subnet/"+c.Name+"/state", "creating") kv.AddInDB(db, "subnet/"+c.Name+"/vpc", c.VPC) kv.AddInDB(db, "subnet/"+c.Name+"/vxlan_id", strconv.Itoa(c.VxlanID)) - kv.AddInDB(db, "subnet/"+c.Name+"/local_ip", c.LocalIP) + kv.AddInDB(db, "subnet/"+c.Name+"/local_iface", localIface) kv.AddInDB(db, "subnet/"+c.Name+"/gateway_ip", c.GatewayIP) kv.AddInDB(db, "subnet/"+c.Name+"/cidr", c.CIDR) return subnet.CreateSubnet(db, c.Name) @@ -33,7 +38,7 @@ type DeleteSubnetCommand struct { Name string } -func (c DeleteSubnetCommand) Execute(db *badger.DB) error { +func (c DeleteSubnetCommand) Execute(db *badger.DB, _ *configuration.Config) error { kv.AddInDB(db, "subnet/"+c.Name+"/state", "deleting") if err := subnet.DeleteSubnet(db, c.Name); err != nil { fmt.Println(err) diff --git a/internal/dispatcher/vpc_commands.go b/internal/dispatcher/vpc_commands.go index 195b014..b2687fc 100644 --- a/internal/dispatcher/vpc_commands.go +++ b/internal/dispatcher/vpc_commands.go @@ -1,6 +1,7 @@ package dispatcher import ( + configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/vpc" "git.g3e.fr/syonad/two/pkg/db/kv" "github.com/dgraph-io/badger/v4" @@ -10,7 +11,7 @@ type CreateVPCCommand struct { Name string } -func (c CreateVPCCommand) Execute(db *badger.DB) error { +func (c CreateVPCCommand) Execute(db *badger.DB, _ *configuration.Config) error { kv.AddInDB(db, "vpc/"+c.Name+"/state", "creating") return vpc.CreateVPC(db, c.Name) } @@ -19,7 +20,7 @@ type DeleteVPCCommand struct { Name string } -func (c DeleteVPCCommand) Execute(db *badger.DB) error { +func (c DeleteVPCCommand) Execute(db *badger.DB, _ *configuration.Config) error { kv.AddInDB(db, "vpc/"+c.Name+"/state", "deleting") if err := vpc.DeleteVPC(db, c.Name); err != nil { return err diff --git a/internal/netif/vxlan.go b/internal/netif/vxlan.go index eacae7c..6523f37 100644 --- a/internal/netif/vxlan.go +++ b/internal/netif/vxlan.go @@ -1,20 +1,22 @@ package netif import ( - "net" - "github.com/vishvananda/netlink" ) -func CreateVxlan(name string, vxlanID int, localIP net.IP) error { +func CreateVxlan(name string, vxlanID int, localIface string) error { + link, err := netlink.LinkByName(localIface) + if err != nil { + return err + } vxlan := &netlink.Vxlan{ LinkAttrs: netlink.LinkAttrs{ Name: name, }, - VxlanId: vxlanID, - Port: 4789, - SrcAddr: localIP, - Learning: false, + VxlanId: vxlanID, + Port: 4789, + VtepDevIndex: link.Attrs().Index, + Learning: false, } return netlink.LinkAdd(vxlan) } diff --git a/internal/subnet/create.go b/internal/subnet/create.go index 9bfe02a..1ed514e 100644 --- a/internal/subnet/create.go +++ b/internal/subnet/create.go @@ -40,13 +40,9 @@ func CreateSubnet(db *badger.DB, subnetName string) error { return fmt.Errorf("parse vxlan_id: %w", err) } - localIPStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/local_ip") + localIface, err := kv.GetFromDB(db, "subnet/"+subnetName+"/local_iface") if err != nil { - return fmt.Errorf("get local_ip: %w", err) - } - localIP := net.ParseIP(localIPStr) - if localIP == nil { - return fmt.Errorf("invalid local_ip: %s", localIPStr) + return fmt.Errorf("get local_iface: %w", err) } gatewayIPStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip") @@ -90,7 +86,7 @@ func CreateSubnet(db *badger.DB, subnetName string) error { } // vxlan - if err := netif.CreateVxlan(vxlanIface, vxlanID, localIP); err != nil { + if err := netif.CreateVxlan(vxlanIface, vxlanID, localIface); err != nil { return fmt.Errorf("create vxlan: %w", err) } diff --git a/internal/vpc/create.go b/internal/vpc/create.go index a6e6aaa..148f70c 100644 --- a/internal/vpc/create.go +++ b/internal/vpc/create.go @@ -1,6 +1,8 @@ package vpc import ( + "strings" + "git.g3e.fr/syonad/two/internal/netif" "git.g3e.fr/syonad/two/internal/netns" "git.g3e.fr/syonad/two/pkg/db/kv" @@ -9,49 +11,40 @@ import ( ) func CreateVPC(db *badger.DB, name string) error { - // missing - // search data in db - // change state in db - - // create netns if state, err := kv.GetFromDB(db, "vpc/"+name+"/state"); err != nil { return err } else if state == "creating" { + vpcID := strings.SplitN(name, "-", 2)[1] + if err := netns.Create(name); err != nil { return err } - // create veth public for this netns - if err := netif.CreateVethToNetns("vp-"+name+"-e", "vp-public-i", "/var/run/netns/"+name, 9000); err != nil { + if err := netif.CreateVethToNetns("vp-"+vpcID+"-e", "vp-"+vpcID+"-i", "/var/run/netns/"+name, 9000); err != nil { return err } - // create public bridge in netns if err := netns.Call(name, func() error { return netif.CreateBridge("br-public", 1500) }); err != nil { return err } - // set veth to ext public bridge - if err := netif.BridgeSetMaster("vp-"+name+"-e", "br-public"); err != nil { + if err := netif.BridgeSetMaster("vp-"+vpcID+"-e", "br-public"); err != nil { return err } - // set veth to int public bridge if err := netns.Call(name, func() error { - return netif.BridgeSetMaster("vp-public-i", "br-public") + return netif.BridgeSetMaster("vp-"+vpcID+"-i", "br-public") }); err != nil { return err } - // set set ext veth up - if err := netif.LinkSetUp("vp-" + name + "-e"); err != nil { + if err := netif.LinkSetUp("vp-" + vpcID + "-e"); err != nil { return err } - // set set int veth up if err := netns.Call(name, func() error { - return netif.LinkSetUp("vp-public-i") + return netif.LinkSetUp("vp-" + vpcID + "-i") }); err != nil { return err } diff --git a/internal/vpc/delete.go b/internal/vpc/delete.go index 10c863d..dbd3a59 100644 --- a/internal/vpc/delete.go +++ b/internal/vpc/delete.go @@ -1,6 +1,8 @@ package vpc import ( + "strings" + "git.g3e.fr/syonad/two/internal/netif" "git.g3e.fr/syonad/two/internal/netns" "git.g3e.fr/syonad/two/pkg/db/kv" @@ -12,7 +14,9 @@ func DeleteVPC(db *badger.DB, name string) error { if state, err := kv.GetFromDB(db, "vpc/"+name+"/state"); err != nil { return err } else if state == "deleting" { - if err := netif.DeleteLink("vp-" + name + "-e"); err != nil { + vpcID := strings.SplitN(name, "-", 2)[1] + + if err := netif.DeleteLink("vp-" + vpcID + "-e"); err != nil { return err }