Skip to content

Commit

Permalink
Make additional announcable cidrs configurable per tenant super netwo…
Browse files Browse the repository at this point in the history
…rk (#562)
  • Loading branch information
majst01 authored Sep 3, 2024
1 parent 58201a8 commit 594f6af
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -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
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ func Test_Migration(t *testing.T) {
ID: "1",
},
}
n = &metal.Network{
Base: metal.Base{
ID: "tenant-super",
Name: "tenant-super",
},
PrivateSuper: true,
}
)

err = rs.UpsertProvisioningEventContainer(ec)
Expand All @@ -76,6 +83,9 @@ func Test_Migration(t *testing.T) {
err = rs.CreateMachine(m)
require.NoError(t, err)

err = rs.CreateNetwork(n)
require.NoError(t, err)

updateM := *m
updateM.Allocation = &metal.MachineAllocation{}
err = rs.UpdateMachine(m, &updateM)
Expand All @@ -89,6 +99,12 @@ func Test_Migration(t *testing.T) {

assert.NotEmpty(t, m.Allocation.UUID, "allocation uuid was not generated")

n, err = rs.FindNetworkByID("tenant-super")
require.NoError(t, err)

assert.NotEmpty(t, n)
assert.Equal(t, []string{"10.240.0.0/12"}, n.AdditionalAnnouncableCIDRs)

ec, err = rs.FindProvisioningEventContainer("1")
require.NoError(t, err)
require.NoError(t, ec.Validate())
Expand Down
3 changes: 3 additions & 0 deletions cmd/metal-api/internal/datastore/network_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
25 changes: 13 additions & 12 deletions cmd/metal-api/internal/metal/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 for private super networks"`
}

// Networks is a list of networks.
Expand All @@ -234,7 +235,7 @@ type NetworkUsage struct {
UsedPrefixes uint64 `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"`
}

// ByID creates an indexed map of partitions where the id is the index.
// ByID creates an indexed map of networks where the id is the index.
func (nws Networks) ByID() NetworkMap {
res := make(NetworkMap)
for i, nw := range nws {
Expand Down
78 changes: 67 additions & 11 deletions cmd/metal-api/internal/service/network-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"fmt"
"log/slog"
"net/http"
"net/netip"
"slices"
"strconv"

mdmv1 "github.com/metal-stack/masterdata-api/api/v1"
mdm "github.com/metal-stack/masterdata-api/pkg/client"
Expand Down Expand Up @@ -104,6 +107,7 @@ func (r *networkResource) webService() *restful.WebService {
To(admin(r.updateNetwork)).
Operation("updateNetwork").
Doc("updates a network. if the network was changed since this one was read, a conflict is returned").
Param(ws.QueryParameter("force", "if true update forcefully").DataType("boolean").DefaultValue("false")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(v1.NetworkUpdateRequest{}).
Returns(http.StatusOK, "OK", v1.NetworkResponse{}).
Expand Down Expand Up @@ -358,6 +362,12 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest
return
}

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 {
Expand All @@ -378,15 +388,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: requestPayload.AdditionalAnnouncableCIDRs,
}

ctx := request.Request.Context()
Expand All @@ -409,6 +420,25 @@ 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) error {
if len(additionalCidrs) == 0 {
return nil
}

if !privateSuper {
return errors.New("additionalannouncablecidrs can only be set in a private super network")
}

for _, cidr := range additionalCidrs {
_, err := netip.ParsePrefix(cidr)
if err != nil {
return fmt.Errorf("given cidr:%q in additionalannouncablecidrs is malformed:%w", cidr, err)
}
}

return nil
}

func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) {
var requestPayload v1.NetworkAllocateRequest
err := request.ReadEntity(&requestPayload)
Expand Down Expand Up @@ -583,8 +613,20 @@ func (r *networkResource) freeNetwork(request *restful.Request, response *restfu
}

func (r *networkResource) updateNetwork(request *restful.Request, response *restful.Response) {
var requestPayload v1.NetworkUpdateRequest
err := request.ReadEntity(&requestPayload)
var (
requestPayload v1.NetworkUpdateRequest
forceParam = request.QueryParameter("force")
)
if forceParam == "" {
forceParam = "false"
}

force, err := strconv.ParseBool(forceParam)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
}
err = request.ReadEntity(&requestPayload)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
Expand Down Expand Up @@ -670,6 +712,20 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest
}
}

err = validateAdditionalAnnouncableCIDRs(requestPayload.AdditionalAnnouncableCIDRs, oldNetwork.PrivateSuper)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
}
for _, oldcidr := range oldNetwork.AdditionalAnnouncableCIDRs {
if !force && !slices.Contains(requestPayload.AdditionalAnnouncableCIDRs, oldcidr) {
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("you cannot remove %q from additionalannouncablecidrs without force flag set", oldcidr)))
return
}
}

newNetwork.AdditionalAnnouncableCIDRs = requestPayload.AdditionalAnnouncableCIDRs

err = r.ds.UpdateNetwork(oldNetwork, &newNetwork)
if err != nil {
r.sendError(request, response, defaultError(err))
Expand Down
Loading

0 comments on commit 594f6af

Please sign in to comment.