Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move size reservations to a dedicated table. #576

Merged
merged 11 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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