Skip to content

Commit 0b8af54

Browse files
committed
IPv6 Support
1 parent 1bf4e40 commit 0b8af54

File tree

11 files changed

+527
-60
lines changed

11 files changed

+527
-60
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package migrations
2+
3+
import (
4+
r "gopkg.in/rethinkdb/rethinkdb-go.v6"
5+
6+
"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
7+
)
8+
9+
func init() {
10+
type tmpPartition struct {
11+
PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength"`
12+
}
13+
datastore.MustRegisterMigration(datastore.Migration{
14+
Name: "migrate partition.childprefixlength to tenant super network",
15+
Version: 6,
16+
Up: func(db *r.Term, session r.QueryExecutor, rs *datastore.RethinkStore) error {
17+
nws, err := rs.ListNetworks()
18+
if err != nil {
19+
return err
20+
}
21+
22+
for _, old := range nws {
23+
if !old.PrivateSuper {
24+
continue
25+
}
26+
27+
cursor, err := db.Table("partition").Get(old.PartitionID).Run(session)
28+
if err != nil {
29+
return err
30+
}
31+
var partition tmpPartition
32+
err = cursor.One(&partition)
33+
if err != nil {
34+
return err
35+
}
36+
37+
new := old
38+
new.ChildPrefixLength = &partition.PrivateNetworkPrefixLength
39+
err = rs.UpdateNetwork(&old, &new)
40+
if err != nil {
41+
return err
42+
}
43+
err = cursor.Close()
44+
if err != nil {
45+
return err
46+
}
47+
}
48+
49+
_, err = db.Table("partition").Replace(r.Row.Without("privatenetworkprefixlength")).RunWrite(session)
50+
return err
51+
},
52+
})
53+
}

cmd/metal-api/internal/metal/network.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ type Network struct {
209209
Base
210210
Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"`
211211
DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"`
212+
ChildPrefixLength *uint8 `rethinkdb:"childprefixlength" json:"childprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes if not nil"`
212213
PartitionID string `rethinkdb:"partitionid" json:"partitionid"`
213214
ProjectID string `rethinkdb:"projectid" json:"projectid"`
214215
ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"`

cmd/metal-api/internal/metal/partition.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package metal
33
// A Partition represents a location.
44
type Partition struct {
55
Base
6-
BootConfiguration BootConfiguration `rethinkdb:"bootconfig" json:"bootconfig"`
7-
MgmtServiceAddress string `rethinkdb:"mgmtserviceaddr" json:"mgmtserviceaddr"`
8-
PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength" json:"privatenetworkprefixlength"`
9-
Labels map[string]string `rethinkdb:"labels" json:"labels"`
6+
BootConfiguration BootConfiguration `rethinkdb:"bootconfig" json:"bootconfig"`
7+
MgmtServiceAddress string `rethinkdb:"mgmtserviceaddr" json:"mgmtserviceaddr"`
8+
Labels map[string]string `rethinkdb:"labels" json:"labels"`
109
}
1110

1211
// BootConfiguration defines the metal-hammer initrd, kernel and commandline

cmd/metal-api/internal/service/ip-service_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ func TestGetIPs(t *testing.T) {
5252
err = json.NewDecoder(resp.Body).Decode(&result)
5353

5454
require.NoError(t, err)
55-
require.Len(t, result, 3)
55+
require.Len(t, result, 4)
5656
require.Equal(t, testdata.IP1.IPAddress, result[0].IPAddress)
5757
require.Equal(t, testdata.IP1.Name, *result[0].Name)
5858
require.Equal(t, testdata.IP2.IPAddress, result[1].IPAddress)
5959
require.Equal(t, testdata.IP2.Name, *result[1].Name)
6060
require.Equal(t, testdata.IP3.IPAddress, result[2].IPAddress)
6161
require.Equal(t, testdata.IP3.Name, *result[2].Name)
62+
require.Equal(t, testdata.IP4.IPAddress, result[3].IPAddress)
63+
require.Equal(t, testdata.IP4.Name, *result[3].Name)
6264
}
6365

6466
func TestGetIP(t *testing.T) {
@@ -85,6 +87,31 @@ func TestGetIP(t *testing.T) {
8587
require.Equal(t, testdata.IP1.Name, *result.Name)
8688
}
8789

90+
func TestGetIPv6(t *testing.T) {
91+
ds, mock := datastore.InitMockDB(t)
92+
testdata.InitMockDBData(mock)
93+
94+
logger := slog.Default()
95+
ipservice, err := NewIP(logger, ds, bus.DirectEndpoints(), ipam.InitTestIpam(t), nil)
96+
require.NoError(t, err)
97+
container := restful.NewContainer().Add(ipservice)
98+
req := httptest.NewRequest("GET", "/v1/ip/2001:0db8:85a3::1", nil)
99+
container = injectViewer(logger, container, req)
100+
w := httptest.NewRecorder()
101+
container.ServeHTTP(w, req)
102+
103+
resp := w.Result()
104+
defer resp.Body.Close()
105+
106+
require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String())
107+
var result v1.IPResponse
108+
err = json.NewDecoder(resp.Body).Decode(&result)
109+
110+
require.NoError(t, err)
111+
require.Equal(t, testdata.IP4.IPAddress, result.IPAddress)
112+
require.Equal(t, testdata.IP4.Name, *result.Name)
113+
}
114+
88115
func TestGetIPNotFound(t *testing.T) {
89116
ds, mock := datastore.InitMockDB(t)
90117
testdata.InitMockDBData(mock)

cmd/metal-api/internal/service/network-service.go

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"log/slog"
88
"net/http"
9+
"net/netip"
910

1011
mdmv1 "github.com/metal-stack/masterdata-api/api/v1"
1112
mdm "github.com/metal-stack/masterdata-api/pkg/client"
@@ -205,6 +206,7 @@ func (r *networkResource) findNetworks(request *restful.Request, response *restf
205206
r.send(request, response, http.StatusOK, result)
206207
}
207208

209+
// TODO allow creation of networks with childprefixlength which are not privatesuper
208210
func (r *networkResource) createNetwork(request *restful.Request, response *restful.Response) {
209211
var requestPayload v1.NetworkCreateRequest
210212
err := request.ReadEntity(&requestPayload)
@@ -260,18 +262,34 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest
260262
}
261263

262264
prefixes := metal.Prefixes{}
263-
// all Prefixes must be valid
265+
addressFamilies := make(map[string]bool)
266+
// all Prefixes must be valid and from the same addressfamily
264267
for i := range requestPayload.Prefixes {
265268
p := requestPayload.Prefixes[i]
266269
prefix, err := metal.NewPrefixFromCIDR(p)
267270
if err != nil {
268271
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err)))
269272
return
270273
}
271-
274+
ipprefix, err := netip.ParsePrefix(p)
275+
if err != nil {
276+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err)))
277+
return
278+
}
279+
if ipprefix.Addr().Is4() {
280+
addressFamilies["ipv4"] = true
281+
}
282+
if ipprefix.Addr().Is6() {
283+
addressFamilies["ipv6"] = true
284+
}
272285
prefixes = append(prefixes, *prefix)
273286
}
274287

288+
if len(addressFamilies) > 1 {
289+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefixes have different addressfamilies")))
290+
return
291+
}
292+
275293
destPrefixes := metal.Prefixes{}
276294
for i := range requestPayload.DestinationPrefixes {
277295
p := requestPayload.DestinationPrefixes[i]
@@ -324,17 +342,38 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest
324342
return
325343
}
326344

345+
// We should support two private super per partition, one per addressfamily
346+
// the network allocate request must be configurable with addressfamily
327347
if privateSuper {
328348
boolTrue := true
329-
err := r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &metal.Network{})
349+
var nw metal.Network
350+
err := r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &nw)
330351
if err != nil {
331352
if !metal.IsNotFound(err) {
332353
r.sendError(request, response, defaultError(err))
333354
return
334355
}
335356
} else {
336-
r.sendError(request, response, defaultError(fmt.Errorf("partition with id %q already has a private super network", partition.ID)))
337-
return
357+
if len(nw.Prefixes) != 0 {
358+
existingsuper := nw.Prefixes[0].String()
359+
360+
ipprefxexistingsuper, err := netip.ParsePrefix(existingsuper)
361+
if err != nil {
362+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not valid: %w", existingsuper, err)))
363+
return
364+
365+
}
366+
newsuper := prefixes[0].String()
367+
ipprefixnew, err := netip.ParsePrefix(newsuper)
368+
if err != nil {
369+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not valid: %w", newsuper, err)))
370+
return
371+
}
372+
if (ipprefixnew.Addr().Is4() && ipprefxexistingsuper.Addr().Is4()) || (ipprefixnew.Addr().Is6() && ipprefxexistingsuper.Addr().Is6()) {
373+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("partition with id %q already has a private super network for this addressfamily", partition.ID)))
374+
return
375+
}
376+
}
338377
}
339378
}
340379
if underlay {
@@ -389,6 +428,23 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest
389428
Labels: labels,
390429
}
391430

431+
// check if childprefixlength is set and matches addressfamily
432+
if requestPayload.ChildPrefixLength != nil && privateSuper {
433+
cpl := *requestPayload.ChildPrefixLength
434+
for _, p := range prefixes {
435+
ipprefix, err := netip.ParsePrefix(p.String())
436+
if err != nil {
437+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err)))
438+
return
439+
}
440+
if cpl <= uint8(ipprefix.Bits()) {
441+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given childprefixlength %d is not greater than prefix length of:%s", cpl, p.String())))
442+
return
443+
}
444+
}
445+
nw.ChildPrefixLength = requestPayload.ChildPrefixLength
446+
}
447+
392448
ctx := request.Request.Context()
393449
for _, p := range nw.Prefixes {
394450
err := r.ipamer.CreatePrefix(ctx, p)
@@ -409,6 +465,7 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest
409465
r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage))
410466
}
411467

468+
// TODO add possibility to allocate from a non super network if given in the AllocateRequest and super has childprefixlength
412469
func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) {
413470
var requestPayload v1.NetworkAllocateRequest
414471
err := request.ReadEntity(&requestPayload)
@@ -463,9 +520,9 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re
463520
return
464521
}
465522

466-
var superNetwork metal.Network
523+
var superNetworks metal.Networks
467524
boolTrue := true
468-
err = r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &superNetwork)
525+
err = r.ds.SearchNetworks(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &superNetworks)
469526
if err != nil {
470527
r.sendError(request, response, defaultError(err))
471528
return
@@ -482,6 +539,37 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re
482539
destPrefixes = append(destPrefixes, *prefix)
483540
}
484541

542+
addressFamily := v1.IPv4AddressFamily
543+
if requestPayload.AddressFamily != nil {
544+
addressFamily = v1.ToAddressFamily(*requestPayload.AddressFamily)
545+
}
546+
547+
r.log.Info("network allocate", "family", addressFamily)
548+
var (
549+
superNetwork metal.Network
550+
superNetworkFound bool
551+
)
552+
for _, snw := range superNetworks {
553+
ipprefix, err := netip.ParsePrefix(snw.Prefixes[0].String())
554+
if err != nil {
555+
r.sendError(request, response, httperrors.BadRequest(err))
556+
return
557+
}
558+
if addressFamily == v1.IPv4AddressFamily && ipprefix.Addr().Is4() {
559+
superNetwork = snw
560+
superNetworkFound = true
561+
}
562+
if addressFamily == v1.IPv6AddressFamily && ipprefix.Addr().Is6() {
563+
superNetwork = snw
564+
superNetworkFound = true
565+
}
566+
}
567+
if !superNetworkFound {
568+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("no supernetwork for addressfamily:%s found", addressFamily)))
569+
return
570+
}
571+
r.log.Info("network allocate", "supernetwork", superNetwork.ID)
572+
485573
nwSpec := &metal.Network{
486574
Base: metal.Base{
487575
Name: name,
@@ -494,8 +582,18 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re
494582
Shared: shared,
495583
Nat: nat,
496584
}
585+
if superNetwork.ChildPrefixLength == nil {
586+
r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("supernetwork %s has no childprefixlength specified", superNetwork.ID)))
587+
}
588+
589+
// Allow configurable prefix length
590+
length := *superNetwork.ChildPrefixLength
591+
if requestPayload.Length != nil {
592+
length = *requestPayload.Length
593+
}
594+
497595
ctx := request.Request.Context()
498-
nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, partition.PrivateNetworkPrefixLength)
596+
nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, length)
499597
if err != nil {
500598
r.sendError(request, response, defaultError(err))
501599
return

0 commit comments

Comments
 (0)