Skip to content

Commit

Permalink
Move size reservations to a dedicated table. (#576)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit91 authored Sep 30, 2024
1 parent 65f0c68 commit 6c79219
Show file tree
Hide file tree
Showing 21 changed files with 2,208 additions and 669 deletions.
25 changes: 17 additions & 8 deletions cmd/metal-api/internal/datastore/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,16 @@ func (rs *RethinkStore) FindWaitingMachine(ctx context.Context, projectid, parti
return nil, err
}

ok := checkSizeReservations(available, projectid, partitionid, partitionMachines.WithSize(size.ID).ByProjectID(), size)
var reservations metal.SizeReservations
err = rs.SearchSizeReservations(&SizeReservationSearchQuery{
Partition: &partitionid,
SizeID: &size.ID,
}, &reservations)
if err != nil {
return nil, err
}

ok := checkSizeReservations(available, projectid, partitionMachines.WithSize(size.ID).ByProjectID(), reservations)
if !ok {
return nil, errors.New("no machine available")
}
Expand All @@ -504,20 +513,20 @@ func (rs *RethinkStore) FindWaitingMachine(ctx context.Context, projectid, parti

// checkSizeReservations returns true when an allocation is possible and
// false when size reservations prevent the allocation for the given project in the given partition
func checkSizeReservations(available metal.Machines, projectid, partitionid string, machinesByProject map[string]metal.Machines, size metal.Size) bool {
if size.Reservations == nil {
func checkSizeReservations(available metal.Machines, projectid string, machinesByProject map[string]metal.Machines, reservations metal.SizeReservations) bool {
if len(reservations) == 0 {
return true
}

var (
reservations = 0
amount = 0
)

for _, r := range size.Reservations.ForPartition(partitionid) {
for _, r := range reservations {
r := r

// sum up the amount of reservations
reservations += r.Amount
amount += r.Amount

alreadyAllocated := len(machinesByProject[r.ProjectID])

Expand All @@ -527,10 +536,10 @@ func checkSizeReservations(available metal.Machines, projectid, partitionid stri
}

// subtract already used up reservations of the project
reservations = max(reservations-alreadyAllocated, 0)
amount = max(amount-alreadyAllocated, 0)
}

return reservations < len(available)
return amount < len(available)
}

func spreadAcrossRacks(allMachines, projectMachines metal.Machines, tags []string) metal.Machines {
Expand Down
43 changes: 20 additions & 23 deletions cmd/metal-api/internal/datastore/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,21 +735,18 @@ func Test_checkSizeReservations(t *testing.T) {
p1 = "1"
p2 = "2"

size = metal.Size{
Base: metal.Base{
ID: "c1-xlarge-x86",
},
Reservations: metal.Reservations{
{
Amount: 1,
ProjectID: p1,
PartitionIDs: []string{partitionA},
},
{
Amount: 2,
ProjectID: p2,
PartitionIDs: []string{partitionA},
},
reservations = metal.SizeReservations{
{
SizeID: "c1-xlarge-x86",
Amount: 1,
ProjectID: p1,
PartitionIDs: []string{partitionA},
},
{
SizeID: "c1-xlarge-x86",
Amount: 2,
ProjectID: p2,
PartitionIDs: []string{partitionA},
},
}

Expand All @@ -764,7 +761,7 @@ func Test_checkSizeReservations(t *testing.T) {
)

// 5 available, 3 reserved, project 0 can allocate
ok := checkSizeReservations(available, p0, partitionA, projectMachines, size)
ok := checkSizeReservations(available, p0, projectMachines, reservations)
require.True(t, ok)
allocate(available[0].ID, p0)

Expand All @@ -781,7 +778,7 @@ func Test_checkSizeReservations(t *testing.T) {
}, projectMachines)

// 4 available, 3 reserved, project 2 can allocate
ok = checkSizeReservations(available, p2, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p2, projectMachines, reservations)
require.True(t, ok)
allocate(available[0].ID, p2)

Expand All @@ -800,7 +797,7 @@ func Test_checkSizeReservations(t *testing.T) {
}, projectMachines)

// 3 available, 3 reserved (1 used), project 0 can allocate
ok = checkSizeReservations(available, p0, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p0, projectMachines, reservations)
require.True(t, ok)
allocate(available[0].ID, p0)

Expand All @@ -819,11 +816,11 @@ func Test_checkSizeReservations(t *testing.T) {
}, projectMachines)

// 2 available, 3 reserved (1 used), project 0 cannot allocate anymore
ok = checkSizeReservations(available, p0, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p0, projectMachines, reservations)
require.False(t, ok)

// 2 available, 3 reserved (1 used), project 2 can allocate
ok = checkSizeReservations(available, p2, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p2, projectMachines, reservations)
require.True(t, ok)
allocate(available[0].ID, p2)

Expand All @@ -842,13 +839,13 @@ func Test_checkSizeReservations(t *testing.T) {
}, projectMachines)

// 1 available, 3 reserved (2 used), project 0 and 2 cannot allocate anymore
ok = checkSizeReservations(available, p0, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p0, projectMachines, reservations)
require.False(t, ok)
ok = checkSizeReservations(available, p2, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p2, projectMachines, reservations)
require.False(t, ok)

// 1 available, 3 reserved (2 used), project 1 can allocate
ok = checkSizeReservations(available, p1, partitionA, projectMachines, size)
ok = checkSizeReservations(available, p1, projectMachines, reservations)
require.True(t, ok)
allocate(available[0].ID, p1)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package migrations

import (
"fmt"

r "gopkg.in/rethinkdb/rethinkdb-go.v6"

"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
)

type OldReservation_Mig07 struct {
Amount int `rethinkdb:"amount" json:"amount"`
Description string `rethinkdb:"description" json:"description"`
ProjectID string `rethinkdb:"projectid" json:"projectid"`
PartitionIDs []string `rethinkdb:"partitionids" json:"partitionids"`
Labels map[string]string `rethinkdb:"labels" json:"labels"`
}

type OldReservations_Mig07 []OldReservation_Mig07

type OldSize_Mig07 struct {
metal.Base
Reservations OldReservations_Mig07 `rethinkdb:"reservations" json:"reservations"`
}

func init() {
getOldSizes := func(db *r.Term, session r.QueryExecutor) ([]OldSize_Mig07, error) {
res, err := db.Table("size").Run(session)
if err != nil {
return nil, err
}
defer res.Close()

var entities []OldSize_Mig07
err = res.All(&entities)
if err != nil {
return nil, fmt.Errorf("cannot fetch all entities: %w", err)
}

return entities, nil
}

datastore.MustRegisterMigration(datastore.Migration{
Name: "migrate size reservations to dedicated table",
Version: 7,
Up: func(db *r.Term, session r.QueryExecutor, rs *datastore.RethinkStore) error {
oldSizes, err := getOldSizes(db, session)
if err != nil {
return err
}

for _, old := range oldSizes {
for _, rv := range old.Reservations {
err = rs.CreateSizeReservation(&metal.SizeReservation{
Base: metal.Base{
ID: "",
Name: "",
Description: rv.Description,
},
SizeID: old.ID,
Amount: rv.Amount,
ProjectID: rv.ProjectID,
PartitionIDs: rv.PartitionIDs,
Labels: rv.Labels,
})
if err != nil {
return err
}
}
}

// now remove the old field

_, err = db.Table("size").Replace(func(row r.Term) r.Term {
return row.Without("reservations")
}).RunWrite(session)
if err != nil {
return err
}

return nil
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore/migrations"
_ "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore/migrations"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
"github.com/metal-stack/metal-api/test"
r "gopkg.in/rethinkdb/rethinkdb-go.v6"

"testing"

Expand Down Expand Up @@ -86,14 +88,36 @@ func Test_Migration(t *testing.T) {
err = rs.CreateNetwork(n)
require.NoError(t, err)

oldSize := migrations.OldSize_Mig07{
Base: metal.Base{
ID: "c1-xlarge-x86",
},
Reservations: []migrations.OldReservation_Mig07{
{
Amount: 3,
Description: "a description",
ProjectID: "project-1",
PartitionIDs: []string{"partition-a"},
Labels: map[string]string{
"a": "b",
},
},
},
}

_, err = r.DB("metal").Table("size").Insert(oldSize).RunWrite(rs.Session())
require.NoError(t, err)

updateM := *m
updateM.Allocation = &metal.MachineAllocation{}
err = rs.UpdateMachine(m, &updateM)
require.NoError(t, err)

// now run the migration
err = rs.Migrate(nil, false)
require.NoError(t, err)

// assert
m, err = rs.FindMachineByID("1")
require.NoError(t, err)

Expand All @@ -105,6 +129,32 @@ func Test_Migration(t *testing.T) {
assert.NotEmpty(t, n)
assert.Equal(t, []string{"10.240.0.0/12"}, n.AdditionalAnnouncableCIDRs)

rvs, err := rs.ListSizeReservations()
require.NoError(t, err)

require.Len(t, rvs, 1)
require.NotEmpty(t, rvs[0].ID)
if diff := cmp.Diff(rvs, metal.SizeReservations{
{
Base: metal.Base{
Description: "a description",
},
SizeID: "c1-xlarge-x86",
Amount: 3,
ProjectID: "project-1",
PartitionIDs: []string{"partition-a"},
Labels: map[string]string{
"a": "b",
},
},
}, cmpopts.IgnoreFields(metal.SizeReservation{}, "ID", "Created", "Changed")); diff != "" {
t.Errorf("size reservations diff: %s", diff)
}

sizes, err := rs.ListSizes()
require.NoError(t, err)
require.Len(t, sizes, 1)

ec, err = rs.FindProvisioningEventContainer("1")
require.NoError(t, err)
require.NoError(t, ec.Validate())
Expand Down
11 changes: 11 additions & 0 deletions cmd/metal-api/internal/datastore/rethinkdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var tables = []string{
"sharedmutex",
"size",
"sizeimageconstraint",
"sizereservation",
"switch",
"switchstatus",
VRFIntegerPool.String(), VRFIntegerPool.String() + "info",
Expand Down Expand Up @@ -78,6 +79,11 @@ func New(log *slog.Logger, dbhost string, dbname string, dbuser string, dbpass s
}
}

// Session exported for migration unit test
func (rs *RethinkStore) Session() r.QueryExecutor {
return rs.session
}

func multi(session r.QueryExecutor, tt ...r.Term) error {
for _, t := range tt {
if err := t.Exec(session); err != nil {
Expand Down Expand Up @@ -214,6 +220,11 @@ func (rs *RethinkStore) sizeImageConstraintTable() *r.Term {
return &res
}

func (rs *RethinkStore) sizeReservationTable() *r.Term {
res := r.DB(rs.dbname).Table("sizereservation")
return &res
}

func (rs *RethinkStore) asnTable() *r.Term {
res := r.DB(rs.dbname).Table(ASNIntegerPool.String())
return &res
Expand Down
Loading

0 comments on commit 6c79219

Please sign in to comment.