From 76a840b80ad44ff3f5cb048d5cdea229630a1d70 Mon Sep 17 00:00:00 2001 From: GnomeZworc Date: Mon, 18 May 2026 22:47:28 +0200 Subject: [PATCH] f-28: add vpc cidr field Signed-off-by: GnomeZworc --- api/agent.yaml | 9 +++++- internal/api/agent/models.go | 2 ++ internal/api/agent/vpc.go | 3 +- internal/api/agent/vpc_test.go | 29 +++++++++++++++++-- internal/api/agent/vpcs.go | 20 +++++++++++-- internal/dispatcher/agent/vpc_commands.go | 8 +++++ .../dispatcher/agent/vpc_commands_test.go | 19 ++++++++++-- internal/subnet/data.go | 25 +++++++++++----- 8 files changed, 98 insertions(+), 17 deletions(-) diff --git a/api/agent.yaml b/api/agent.yaml index 44a5c80..e3bf8b9 100644 --- a/api/agent.yaml +++ b/api/agent.yaml @@ -294,13 +294,17 @@ components: VPCCreateRequest: type: object - required: [name] + required: [name, cidr] properties: name: type: string description: Unique name for the VPC, must follow the format vp-[id] pattern: '^vp-.+' example: vp-00001 + cidr: + type: string + description: CIDR block for the entire VPC address space + example: "10.0.0.0/16" VPC: type: object @@ -312,6 +316,9 @@ components: type: string enum: [creating, created, deleting, deleted] example: created + cidr: + type: string + example: "10.0.0.0/16" SubnetCreateRequest: type: object diff --git a/internal/api/agent/models.go b/internal/api/agent/models.go index 4621f1c..ffa994a 100644 --- a/internal/api/agent/models.go +++ b/internal/api/agent/models.go @@ -2,11 +2,13 @@ package agentapi type VPCCreateRequest struct { Name string `json:"name"` + CIDR string `json:"cidr"` } type VPC struct { Name string `json:"name"` State string `json:"state"` + CIDR string `json:"cidr"` } type SubnetCreateRequest struct { diff --git a/internal/api/agent/vpc.go b/internal/api/agent/vpc.go index 43cc33c..bb3f5d7 100644 --- a/internal/api/agent/vpc.go +++ b/internal/api/agent/vpc.go @@ -35,8 +35,9 @@ func (s *Server) getVpc(w http.ResponseWriter, _ *http.Request, name string) { json.NewEncoder(w).Encode(ErrorResponse{Error: "vpc not found"}) return } + cidr, _ := kv.GetFromDB(s.db, "vpc/"+name+"/cidr") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(VPC{Name: name, State: state}) + json.NewEncoder(w).Encode(VPC{Name: name, State: state, CIDR: cidr}) } func (s *Server) deleteVpc(w http.ResponseWriter, _ *http.Request, name string) { diff --git a/internal/api/agent/vpc_test.go b/internal/api/agent/vpc_test.go index 01308a5..3516305 100644 --- a/internal/api/agent/vpc_test.go +++ b/internal/api/agent/vpc_test.go @@ -53,7 +53,7 @@ func TestListVpcs_InvalidMethod(t *testing.T) { func TestPostVpc_Created(t *testing.T) { s, _ := newTestServer(t) - body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-new"}) + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-new", CIDR: "10.0.0.0/16"}) w := httptest.NewRecorder() s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) if w.Code != http.StatusAccepted { @@ -67,11 +67,34 @@ func TestPostVpc_Created(t *testing.T) { if result.State != "creating" { t.Errorf("state attendu creating, obtenu %q", result.State) } + if result.CIDR != "10.0.0.0/16" { + t.Errorf("cidr attendu 10.0.0.0/16, obtenu %q", result.CIDR) + } } func TestPostVpc_MissingName(t *testing.T) { s, _ := newTestServer(t) - body, _ := json.Marshal(VPCCreateRequest{}) + body, _ := json.Marshal(VPCCreateRequest{CIDR: "10.0.0.0/16"}) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +func TestPostVpc_MissingCIDR(t *testing.T) { + s, _ := newTestServer(t) + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-new"}) + w := httptest.NewRecorder() + s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) + if w.Code != http.StatusBadRequest { + t.Errorf("attendu 400, obtenu %d", w.Code) + } +} + +func TestPostVpc_InvalidCIDR(t *testing.T) { + s, _ := newTestServer(t) + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-new", CIDR: "not-a-cidr"}) w := httptest.NewRecorder() s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) if w.Code != http.StatusBadRequest { @@ -82,7 +105,7 @@ func TestPostVpc_MissingName(t *testing.T) { func TestPostVpc_Duplicate(t *testing.T) { s, db := newTestServer(t) kv.AddInDB(db, "vpc/vpc-exist/state", "created") - body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-exist"}) + body, _ := json.Marshal(VPCCreateRequest{Name: "vpc-exist", CIDR: "10.0.0.0/16"}) w := httptest.NewRecorder() s.VpcsHandler(w, httptest.NewRequest(http.MethodPost, "/vpcs", bytes.NewReader(body))) if w.Code != http.StatusConflict { diff --git a/internal/api/agent/vpcs.go b/internal/api/agent/vpcs.go index b062d1c..905cdee 100644 --- a/internal/api/agent/vpcs.go +++ b/internal/api/agent/vpcs.go @@ -2,6 +2,7 @@ package agentapi import ( "encoding/json" + "net" "net/http" "strings" @@ -38,8 +39,11 @@ func (s *Server) listVpcs(w http.ResponseWriter, _ *http.Request) { if _, ok := vpcs[name]; !ok { vpcs[name] = &VPC{Name: name} } - if parts[2] == "state" { + switch parts[2] { + case "state": vpcs[name].State = value + case "cidr": + vpcs[name].CIDR = value } } result := make([]VPC, 0, len(vpcs)) @@ -62,7 +66,17 @@ func (s *Server) postVpc(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(ErrorResponse{Error: "name is required"}) return } - cmd := dispatcher.CreateVPCCommand{Name: req.Name} + if req.CIDR == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "cidr is required"}) + return + } + if _, _, err := net.ParseCIDR(req.CIDR); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid cidr"}) + return + } + cmd := dispatcher.CreateVPCCommand{Name: req.Name, CIDR: req.CIDR} if err := s.dispatcher.Prepare(cmd); err != nil { w.WriteHeader(http.StatusConflict) json.NewEncoder(w).Encode(ErrorResponse{Error: err.Error()}) @@ -76,5 +90,5 @@ func (s *Server) postVpc(w http.ResponseWriter, r *http.Request) { return } w.WriteHeader(http.StatusAccepted) - json.NewEncoder(w).Encode(VPC{Name: req.Name, State: state}) + json.NewEncoder(w).Encode(VPC{Name: req.Name, State: state, CIDR: req.CIDR}) } diff --git a/internal/dispatcher/agent/vpc_commands.go b/internal/dispatcher/agent/vpc_commands.go index c03dd77..a129d88 100644 --- a/internal/dispatcher/agent/vpc_commands.go +++ b/internal/dispatcher/agent/vpc_commands.go @@ -2,6 +2,7 @@ package dispatcher import ( "fmt" + "net" "strings" "time" @@ -13,12 +14,19 @@ import ( type CreateVPCCommand struct { Name string + CIDR string } func (c CreateVPCCommand) Prepare(db *badger.DB, _ *configuration.Config) error { if _, err := kv.GetFromDB(db, "vpc/"+c.Name+"/state"); err == nil { return fmt.Errorf("vpc %q already exists", c.Name) } + if _, _, err := net.ParseCIDR(c.CIDR); err != nil { + return fmt.Errorf("invalid cidr %q: %w", c.CIDR, err) + } + if err := kv.AddInDB(db, "vpc/"+c.Name+"/cidr", c.CIDR); err != nil { + return err + } return kv.AddInDB(db, "vpc/"+c.Name+"/state", "creating") } diff --git a/internal/dispatcher/agent/vpc_commands_test.go b/internal/dispatcher/agent/vpc_commands_test.go index e2f2162..9633733 100644 --- a/internal/dispatcher/agent/vpc_commands_test.go +++ b/internal/dispatcher/agent/vpc_commands_test.go @@ -10,7 +10,7 @@ import ( func TestCreateVPCCommand_Prepare_NewVPC(t *testing.T) { _, db := newTestDispatcher(t) - cmd := CreateVPCCommand{Name: "vpc-1"} + cmd := CreateVPCCommand{Name: "vpc-1", CIDR: "10.0.0.0/16"} if err := cmd.Prepare(db, nil); err != nil { t.Fatalf("Prepare a échoué : %v", err) } @@ -21,17 +21,32 @@ func TestCreateVPCCommand_Prepare_NewVPC(t *testing.T) { if state != "creating" { t.Errorf("state attendu creating, obtenu %q", state) } + cidr, err := kv.GetFromDB(db, "vpc/vpc-1/cidr") + if err != nil { + t.Fatalf("cidr non écrit en DB : %v", err) + } + if cidr != "10.0.0.0/16" { + t.Errorf("cidr attendu 10.0.0.0/16, obtenu %q", cidr) + } } func TestCreateVPCCommand_Prepare_Duplicate(t *testing.T) { _, db := newTestDispatcher(t) kv.AddInDB(db, "vpc/vpc-exist/state", "created") - cmd := CreateVPCCommand{Name: "vpc-exist"} + cmd := CreateVPCCommand{Name: "vpc-exist", CIDR: "10.0.0.0/16"} if err := cmd.Prepare(db, nil); err == nil { t.Error("Prepare devrait échouer sur un VPC déjà existant") } } +func TestCreateVPCCommand_Prepare_InvalidCIDR(t *testing.T) { + _, db := newTestDispatcher(t) + cmd := CreateVPCCommand{Name: "vpc-bad", CIDR: "not-a-cidr"} + if err := cmd.Prepare(db, nil); err == nil { + t.Error("Prepare devrait échouer avec un CIDR invalide") + } +} + // --- DeleteVPCCommand.Prepare --- func TestDeleteVPCCommand_Prepare_Success(t *testing.T) { diff --git a/internal/subnet/data.go b/internal/subnet/data.go index 849b141..fbc45ff 100644 --- a/internal/subnet/data.go +++ b/internal/subnet/data.go @@ -11,14 +11,15 @@ import ( ) type subnetData struct { - vpc string - subnetID string - bridge string - mode string - vxlanID int - localIface string + vpc string + subnetID string + bridge string + mode string + vxlanID int + localIface string interfaceIP net.IP - cidr *net.IPNet + cidr *net.IPNet + vpcCIDR *net.IPNet } func loadSubnet(db *badger.DB, name string) (subnetData, error) { @@ -77,5 +78,15 @@ func loadSubnet(db *badger.DB, name string) (subnetData, error) { } d.cidr = ipNet + vpcCIDRStr, err := kv.GetFromDB(db, "vpc/"+d.vpc+"/cidr") + if err != nil { + return d, fmt.Errorf("get vpc cidr: %w", err) + } + _, vpcIPNet, err := net.ParseCIDR(vpcCIDRStr) + if err != nil { + return d, fmt.Errorf("parse vpc cidr: %w", err) + } + d.vpcCIDR = vpcIPNet + return d, nil }