From 6430ea1766c4c75c1f9776525001cf3358a95d77 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 14 Aug 2024 12:48:51 +0200 Subject: [PATCH] Make additional announcable cidrs configurable per tenant super network --- .../06_additional_announcable_cidrs.go | 40 +++++++++++ .../datastore/network_integration_test.go | 3 + cmd/metal-api/internal/metal/network.go | 23 ++++--- .../internal/service/network-service.go | 50 +++++++++++--- .../internal/service/switch-service.go | 67 ++++++++++++------- .../internal/service/switch-service_test.go | 50 +++++++++----- cmd/metal-api/internal/service/v1/network.go | 41 ++++++------ cmd/metal-api/internal/testdata/testdata.go | 15 +++-- go.mod | 17 ++--- go.sum | 32 +++++---- spec/metal-api.json | 28 ++++++++ 11 files changed, 256 insertions(+), 110 deletions(-) create mode 100644 cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go diff --git a/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go b/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go new file mode 100644 index 000000000..0ddc3bb5b --- /dev/null +++ b/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go @@ -0,0 +1,40 @@ +package migrations + +import ( + r "gopkg.in/rethinkdb/rethinkdb-go.v6" + + "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" +) + +func init() { + datastore.MustRegisterMigration(datastore.Migration{ + Name: "migrate super tenant networks to contain additionannouncablecidrs", + Version: 6, + Up: func(db *r.Term, session r.QueryExecutor, rs *datastore.RethinkStore) error { + nws, err := rs.ListNetworks() + if err != nil { + return err + } + + for _, old := range nws { + if !old.PrivateSuper { + continue + } + new := old + + if len(old.AdditionalAnnouncableCIDRs) == 0 { + new.AdditionalAnnouncableCIDRs = []string{ + // This was the previous hard coded default in metal-core + "10.240.0.0/12", + } + } + + err = rs.UpdateNetwork(&old, &new) + if err != nil { + return err + } + } + return nil + }, + }) +} diff --git a/cmd/metal-api/internal/datastore/network_integration_test.go b/cmd/metal-api/internal/datastore/network_integration_test.go index a756df908..782612997 100644 --- a/cmd/metal-api/internal/datastore/network_integration_test.go +++ b/cmd/metal-api/internal/datastore/network_integration_test.go @@ -65,6 +65,9 @@ func (_ *networkTestable) defaultBody(n *metal.Network) *metal.Network { if n.DestinationPrefixes == nil { n.DestinationPrefixes = metal.Prefixes{} } + if n.AdditionalAnnouncableCIDRs == nil { + n.AdditionalAnnouncableCIDRs = []string{} + } return n } diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 9f2477d71..22d344bfc 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -207,17 +207,18 @@ func (p *Prefix) equals(other *Prefix) bool { // TODO specify rethinkdb restrictions. type Network struct { Base - Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"` - DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"` - PartitionID string `rethinkdb:"partitionid" json:"partitionid"` - ProjectID string `rethinkdb:"projectid" json:"projectid"` - ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"` - Vrf uint `rethinkdb:"vrf" json:"vrf"` - PrivateSuper bool `rethinkdb:"privatesuper" json:"privatesuper"` - Nat bool `rethinkdb:"nat" json:"nat"` - Underlay bool `rethinkdb:"underlay" json:"underlay"` - Shared bool `rethinkdb:"shared" json:"shared"` - Labels map[string]string `rethinkdb:"labels" json:"labels"` + Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"` + DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"` + PartitionID string `rethinkdb:"partitionid" json:"partitionid"` + ProjectID string `rethinkdb:"projectid" json:"projectid"` + ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"` + Vrf uint `rethinkdb:"vrf" json:"vrf"` + PrivateSuper bool `rethinkdb:"privatesuper" json:"privatesuper"` + Nat bool `rethinkdb:"nat" json:"nat"` + Underlay bool `rethinkdb:"underlay" json:"underlay"` + Shared bool `rethinkdb:"shared" json:"shared"` + Labels map[string]string `rethinkdb:"labels" json:"labels"` + AdditionalAnnouncableCIDRs []string `rethinkdb:"additionalannouncablecidrs" json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork"` } // Networks is a list of networks. diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index bd4db1d64..9203742d6 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/http" + "net/netip" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdm "github.com/metal-stack/masterdata-api/pkg/client" @@ -358,6 +359,12 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } + additionalAnnouncableCIDRs, err := validateAdditionalAnnouncableCIDRs(requestPayload.AdditionalAnnouncableCIDRs, privateSuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + if vrf != 0 { err = acquireVRF(r.ds, vrf) if err != nil { @@ -378,15 +385,16 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest Name: name, Description: description, }, - Prefixes: prefixes, - DestinationPrefixes: destPrefixes, - PartitionID: partitionID, - ProjectID: projectID, - Nat: nat, - PrivateSuper: privateSuper, - Underlay: underlay, - Vrf: vrf, - Labels: labels, + Prefixes: prefixes, + DestinationPrefixes: destPrefixes, + PartitionID: partitionID, + ProjectID: projectID, + Nat: nat, + PrivateSuper: privateSuper, + Underlay: underlay, + Vrf: vrf, + Labels: labels, + AdditionalAnnouncableCIDRs: additionalAnnouncableCIDRs, } ctx := request.Request.Context() @@ -409,6 +417,23 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } +func validateAdditionalAnnouncableCIDRs(additionalCidrs []string, privateSuper bool) ([]string, error) { + var result []string + if len(additionalCidrs) > 0 { + if !privateSuper { + return nil, errors.New("additionalannouncablecidrs can only be set in a private super network") + } + for _, cidr := range additionalCidrs { + _, err := netip.ParsePrefix(cidr) + if err != nil { + return nil, fmt.Errorf("given cidr:%q in additionalannouncablecidrs is malformed:%w", cidr, err) + } + result = append(result, cidr) + } + } + return result, nil +} + func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) { var requestPayload v1.NetworkAllocateRequest err := request.ReadEntity(&requestPayload) @@ -670,6 +695,13 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest } } + additionalAnnouncableCIDRs, err := validateAdditionalAnnouncableCIDRs(requestPayload.AdditionalAnnouncableCIDRs, oldNetwork.PrivateSuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + newNetwork.AdditionalAnnouncableCIDRs = additionalAnnouncableCIDRs + err = r.ds.UpdateNetwork(oldNetwork, &newNetwork) if err != nil { r.sendError(request, response, defaultError(err)) diff --git a/cmd/metal-api/internal/service/switch-service.go b/cmd/metal-api/internal/service/switch-service.go index 8fea0f965..4309c2d61 100644 --- a/cmd/metal-api/internal/service/switch-service.go +++ b/cmd/metal-api/internal/service/switch-service.go @@ -144,7 +144,7 @@ func (r *switchResource) findSwitch(request *restful.Request, response *restful. return } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -160,7 +160,7 @@ func (r *switchResource) listSwitches(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponseList(ss, r.ds) + resp, err := r.makeSwitchResponseList(ss) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -184,7 +184,7 @@ func (r *switchResource) findSwitches(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponseList(ss, r.ds) + resp, err := r.makeSwitchResponseList(ss) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -223,7 +223,7 @@ func (r *switchResource) deleteSwitch(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -388,7 +388,7 @@ func (r *switchResource) toggleSwitchPort(request *restful.Request, response *re } } - resp, err := makeSwitchResponse(&newSwitch, r.ds) + resp, err := r.makeSwitchResponse(&newSwitch) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -437,7 +437,7 @@ func (r *switchResource) updateSwitch(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponse(&newSwitch, r.ds) + resp, err := r.makeSwitchResponse(&newSwitch) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -553,7 +553,7 @@ func (r *switchResource) registerSwitch(request *restful.Request, response *rest } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -772,22 +772,22 @@ func updateSwitchNics(oldNics, newNics map[string]*metal.Nic, currentConnections return finalNics, nil } -func makeSwitchResponse(s *metal.Switch, ds *datastore.RethinkStore) (*v1.SwitchResponse, error) { - p, ips, machines, ss, err := findSwitchReferencedEntities(s, ds) +func (r *switchResource) makeSwitchResponse(s *metal.Switch) (*v1.SwitchResponse, error) { + p, ips, machines, ss, err := findSwitchReferencedEntities(s, r.ds) if err != nil { return nil, err } - nics, err := makeSwitchNics(s, ips, machines) + nics, err := r.makeSwitchNics(s, ips, machines) if err != nil { return nil, err } - cons := makeSwitchCons(s) + cons := r.makeSwitchCons(s) return v1.NewSwitchResponse(s, ss, p, nics, cons), nil } -func makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { vnis := []string{} cidrs := []string{} @@ -809,7 +809,7 @@ func makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { return v1.NewBGPFilter(vnis, cidrs), nil } -func makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, error) { vnis := []string{} cidrs := []string{} @@ -826,6 +826,23 @@ func makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, erro // Allow all prefixes of the private network if private != nil { cidrs = append(cidrs, private.Prefixes...) + + privateNetwork, err := r.ds.FindNetworkByID(private.NetworkID) + if err != nil && !metal.IsNotFound(err) { + return v1.BGPFilter{}, err + } + if privateNetwork != nil { + parentNetwork, err := r.ds.FindNetworkByID(privateNetwork.ParentNetworkID) + if err != nil && !metal.IsNotFound(err) { + return v1.BGPFilter{}, err + } + // Only for private networks, AdditionalAnnouncableCIDRs are applied. + // they contain usually the pod- and service- cidrs in a kubernetes cluster + if parentNetwork != nil && len(parentNetwork.AdditionalAnnouncableCIDRs) > 0 { + r.log.Debug("makeBGPFilterMachine", "additional cidrs", parentNetwork.AdditionalAnnouncableCIDRs) + cidrs = append(cidrs, parentNetwork.AdditionalAnnouncableCIDRs...) + } + } } for _, i := range ips[m.Allocation.Project] { // No need to add /32 addresses of the primary network to the whitelist. @@ -884,7 +901,7 @@ func compactCidrs(cidrs []string) ([]string, error) { return compactedCidrs, nil } -func makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, error) { var ( filter v1.BGPFilter err error @@ -894,16 +911,16 @@ func makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, // vrf "default" means: the firewall was successfully allocated and the switch port configured // otherwise the port is still not configured yet (pxe-setup) and a BGPFilter would break the install routine if vrf == "default" { - filter, err = makeBGPFilterFirewall(m) + filter, err = r.makeBGPFilterFirewall(m) } } else { - filter, err = makeBGPFilterMachine(m, ips) + filter, err = r.makeBGPFilterMachine(m, ips) } return filter, err } -func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) (v1.SwitchNics, error) { +func (r *switchResource) makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) (v1.SwitchNics, error) { machinesByID := map[string]*metal.Machine{} for i, m := range machines { machinesByID[m.ID] = &machines[i] @@ -924,7 +941,7 @@ func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) m := machinesBySwp[n.Name] var filter *v1.BGPFilter if m != nil && m.Allocation != nil { - f, err := makeBGPFilter(*m, n.Vrf, ips) + f, err := r.makeBGPFilter(*m, n.Vrf, ips) if err != nil { return nil, err } @@ -955,7 +972,7 @@ func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) return nics, nil } -func makeSwitchCons(s *metal.Switch) []v1.SwitchConnection { +func (r *switchResource) makeSwitchCons(s *metal.Switch) []v1.SwitchConnection { cons := []v1.SwitchConnection{} nicMap := s.Nics.ByName() @@ -1026,14 +1043,14 @@ func findSwitchReferencedEntities(s *metal.Switch, ds *datastore.RethinkStore) ( return p, ips.ByProjectID(), m, ss, nil } -func makeSwitchResponseList(ss metal.Switches, ds *datastore.RethinkStore) ([]*v1.SwitchResponse, error) { - pMap, ips, err := getSwitchReferencedEntityMaps(ds) +func (r *switchResource) makeSwitchResponseList(ss metal.Switches) ([]*v1.SwitchResponse, error) { + pMap, ips, err := getSwitchReferencedEntityMaps(r.ds) if err != nil { return nil, err } result := []*v1.SwitchResponse{} - m, err := ds.ListMachines() + m, err := r.ds.ListMachines() if err != nil { return nil, fmt.Errorf("could not find machines: %w", err) } @@ -1046,12 +1063,12 @@ func makeSwitchResponseList(ss metal.Switches, ds *datastore.RethinkStore) ([]*v p = &partitionEntity } - nics, err := makeSwitchNics(&sw, ips, m) + nics, err := r.makeSwitchNics(&sw, ips, m) if err != nil { return nil, err } - cons := makeSwitchCons(&sw) - ss, err := ds.GetSwitchStatus(sw.ID) + cons := r.makeSwitchCons(&sw) + ss, err := r.ds.GetSwitchStatus(sw.ID) if err != nil && !metal.IsNotFound(err) { return nil, err } diff --git a/cmd/metal-api/internal/service/switch-service_test.go b/cmd/metal-api/internal/service/switch-service_test.go index 8d003d12b..054770527 100644 --- a/cmd/metal-api/internal/service/switch-service_test.go +++ b/cmd/metal-api/internal/service/switch-service_test.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/http" "net/http/httptest" + "os" "reflect" "testing" "time" @@ -403,7 +404,8 @@ func TestMakeBGPFilterFirewall(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeBGPFilterFirewall(tt.args.machine) + r := switchResource{} + got, _ := r.makeBGPFilterFirewall(tt.args.machine) if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeBGPFilterFirewall() = %v, want %v", got, tt.want) } @@ -412,6 +414,8 @@ func TestMakeBGPFilterFirewall(t *testing.T) { } func TestMakeBGPFilterMachine(t *testing.T) { + ds, mock := datastore.InitMockDB(t) + type args struct { machine metal.Machine ipsMap metal.IPsMap @@ -446,29 +450,33 @@ func TestMakeBGPFilterMachine(t *testing.T) { Project: "project", MachineNetworks: []*metal.MachineNetwork{ { - IPs: []string{"10.1.0.1"}, - Prefixes: []string{"10.2.0.0/22", "10.1.0.0/22"}, - Vrf: 1234, - Private: true, + NetworkID: "1", + IPs: []string{"10.1.0.1"}, + Prefixes: []string{"10.2.0.0/22", "10.1.0.0/22"}, + Vrf: 1234, + Private: true, }, { - IPs: []string{"10.0.0.2", "10.0.0.1"}, - Vrf: 0, - Underlay: true, + NetworkID: "2", + IPs: []string{"10.0.0.2", "10.0.0.1"}, + Vrf: 0, + Underlay: true, }, { - IPs: []string{"212.89.42.2", "212.89.42.1"}, - Vrf: 104009, + NetworkID: "3", + IPs: []string{"212.89.42.2", "212.89.42.1"}, + Vrf: 104009, }, { - IPs: []string{"2001::"}, - Vrf: 104010, + NetworkID: "4", + IPs: []string{"2001::"}, + Vrf: 104010, }, }, }, }, }, - want: v1.NewBGPFilter([]string{}, []string{"10.1.0.0/22", "10.2.0.0/22", "100.127.1.1/32", "2001::1/128", "212.89.42.1/32", "212.89.42.2/32"}), + want: v1.NewBGPFilter([]string{}, []string{"10.1.0.0/22", "10.2.0.0/22", "100.127.1.1/32", "10.240.0.0/12", "2001::1/128", "212.89.42.1/32", "212.89.42.2/32"}), }, { name: "allow only allocated ips", @@ -483,8 +491,9 @@ func TestMakeBGPFilterMachine(t *testing.T) { Project: "project", MachineNetworks: []*metal.MachineNetwork{ { - IPs: []string{"212.89.42.2", "212.89.42.1"}, - Vrf: 104009, + NetworkID: "5", + IPs: []string{"212.89.42.2", "212.89.42.1"}, + Vrf: 104009, }, }, }, @@ -493,10 +502,16 @@ func TestMakeBGPFilterMachine(t *testing.T) { want: v1.NewBGPFilter([]string{}, []string{"212.89.42.1/32"}), }, } + for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeBGPFilterMachine(tt.args.machine, tt.args.ipsMap) + mock.On(r.DB("mockdb").Table("network").Get(r.MockAnything())).Return(testdata.Partition1PrivateSuperNetwork, nil) + + r := switchResource{webResource: webResource{ds: ds, log: slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))}} + + got, _ := r.makeBGPFilterMachine(tt.args.machine, tt.args.ipsMap) + if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeBGPFilterMachine() = %v, want %v", got, tt.want) } @@ -603,7 +618,8 @@ func TestMakeSwitchNics(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeSwitchNics(tt.args.s, tt.args.ips, tt.args.machines) + r := switchResource{} + got, _ := r.makeSwitchNics(tt.args.s, tt.args.ips, tt.args.machines) if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeSwitchNics() = %v, want %v", got, tt.want) } diff --git a/cmd/metal-api/internal/service/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index d1bb9cc73..911e3e540 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -15,14 +15,15 @@ type NetworkBase struct { // NetworkImmutable defines the properties which are immutable in the Network. type NetworkImmutable struct { - Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` - DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` - Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` - PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` - Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` - Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` - VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` - ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` + Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` + DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` + Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` + PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` + Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` + Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` + VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` + ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` + AdditionalAnnouncableCIDRs []string `json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork" optional:"true"` } // NetworkUsage reports core metrics about available and used IPs or Prefixes in a Network. @@ -57,10 +58,11 @@ type NetworkFindRequest struct { // NetworkUpdateRequest defines the properties of a Network which can be updated. type NetworkUpdateRequest struct { Common - Prefixes []string `json:"prefixes" description:"the prefixes of this network" optional:"true"` - DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this network." optional:"true"` - Shared *bool `json:"shared" description:"marks a network as shareable." optional:"true"` + Prefixes []string `json:"prefixes" description:"the prefixes of this network" optional:"true"` + DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this network." optional:"true"` + Shared *bool `json:"shared" description:"marks a network as shareable." optional:"true"` + AdditionalAnnouncableCIDRs []string `json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork" optional:"true"` } // NetworkResponse holds all properties returned in a FindNetwork or GetNetwork request. @@ -104,13 +106,14 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw Shared: &network.Shared, }, NetworkImmutable: NetworkImmutable{ - Prefixes: network.Prefixes.String(), - DestinationPrefixes: network.DestinationPrefixes.String(), - Nat: network.Nat, - PrivateSuper: network.PrivateSuper, - Underlay: network.Underlay, - Vrf: &network.Vrf, - ParentNetworkID: parentNetworkID, + Prefixes: network.Prefixes.String(), + DestinationPrefixes: network.DestinationPrefixes.String(), + Nat: network.Nat, + PrivateSuper: network.PrivateSuper, + Underlay: network.Underlay, + Vrf: &network.Vrf, + ParentNetworkID: parentNetworkID, + AdditionalAnnouncableCIDRs: network.AdditionalAnnouncableCIDRs, }, Usage: NetworkUsage{ AvailableIPs: usage.AvailableIPs, diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index 273f5bfb0..3002ac734 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -323,13 +323,14 @@ var ( Base: metal.Base{ ID: "super-tenant-network-1", }, - Prefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "16"}}, - PartitionID: Partition1.ID, - ParentNetworkID: "", - ProjectID: "", - PrivateSuper: true, - Nat: false, - Underlay: false, + Prefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "16"}}, + PartitionID: Partition1.ID, + ParentNetworkID: "", + ProjectID: "", + PrivateSuper: true, + Nat: false, + Underlay: false, + AdditionalAnnouncableCIDRs: []string{"10.240.0.0/12"}, } Partition2PrivateSuperNetwork = metal.Network{ diff --git a/go.mod b/go.mod index dfd9688d1..b738718f3 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( github.com/metal-stack/security v0.8.1 github.com/metal-stack/v v1.0.3 github.com/nsqio/go-nsq v1.1.0 - github.com/prometheus/client_golang v1.19.1 - github.com/samber/lo v1.46.0 + github.com/prometheus/client_golang v1.20.0 + github.com/samber/lo v1.47.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 @@ -75,7 +75,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker v27.1.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -94,7 +94,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/gofrs/uuid/v5 v5.2.0 // indirect + github.com/gofrs/uuid/v5 v5.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -142,6 +142,7 @@ require ( github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.2.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -200,15 +201,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.23.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.6.0 // indirect; indirecct golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/cenkalti/backoff.v2 v2.2.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index f8cb449d6..1fb0b970e 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= +github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -161,8 +161,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -256,6 +256,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -317,6 +319,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -366,8 +370,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -391,8 +395,8 @@ github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3 github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -580,8 +584,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -621,10 +625,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE= -google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/spec/metal-api.json b/spec/metal-api.json index deac2690e..d05e69e14 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -3613,6 +3613,13 @@ }, "v1.NetworkCreateRequest": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "description": { "description": "a description for this entity", "type": "string" @@ -3746,6 +3753,13 @@ "v1.NetworkImmutable": { "description": "a network which contains prefixes from which IP addresses can be allocated\nprefixes that are reachable within this network", "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "destinationprefixes": { "description": "the destination prefixes of this network", "items": { @@ -3796,6 +3810,13 @@ }, "v1.NetworkResponse": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "changed": { "description": "the last changed timestamp of this entity", "format": "date-time", @@ -3895,6 +3916,13 @@ }, "v1.NetworkUpdateRequest": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "description": { "description": "a description for this entity", "type": "string"