diff --git a/cmd/metal-api/internal/datastore/integer.go b/cmd/metal-api/internal/datastore/integer.go index 818db0670..840a01c93 100644 --- a/cmd/metal-api/internal/datastore/integer.go +++ b/cmd/metal-api/internal/datastore/integer.go @@ -225,7 +225,7 @@ func makeRange(min, max uint) []integer { a := make([]integer, max-min+1) for i := range a { a[i] = integer{ - ID: min + uint(i), + ID: min + uint(i), // nolint:gosec } } return a diff --git a/cmd/metal-api/internal/datastore/machine.go b/cmd/metal-api/internal/datastore/machine.go index 1a30c939f..eb208e942 100644 --- a/cmd/metal-api/internal/datastore/machine.go +++ b/cmd/metal-api/internal/datastore/machine.go @@ -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") } @@ -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]) @@ -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 { diff --git a/cmd/metal-api/internal/datastore/machine_integration_test.go b/cmd/metal-api/internal/datastore/machine_integration_test.go index 643f0f843..b95dc5ffe 100644 --- a/cmd/metal-api/internal/datastore/machine_integration_test.go +++ b/cmd/metal-api/internal/datastore/machine_integration_test.go @@ -120,6 +120,9 @@ func (_ *machineTestable) defaultBody(m *metal.Machine) *metal.Machine { } } } + if m.IPMI.PowerSupplies == nil { + m.IPMI.PowerSupplies = metal.PowerSupplies{} + } return m } @@ -936,6 +939,7 @@ func TestRethinkStore_UpdateMachine(t *testing.T) { want: &metal.Machine{ Base: metal.Base{ID: "1"}, Hardware: metal.MachineHardware{Nics: metal.Nics{}, Disks: []metal.BlockDevice{}, MetalCPUs: []metal.MetalCPU{}, MetalGPUs: []metal.MetalGPU{}}, + IPMI: metal.IPMI{PowerSupplies: metal.PowerSupplies{}}, Tags: []string{"a=b"}, }, }, diff --git a/cmd/metal-api/internal/datastore/machine_test.go b/cmd/metal-api/internal/datastore/machine_test.go index b745d10e1..30ff5c507 100644 --- a/cmd/metal-api/internal/datastore/machine_test.go +++ b/cmd/metal-api/internal/datastore/machine_test.go @@ -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}, }, } @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/cmd/metal-api/internal/datastore/migrations/07_size_reservations_table.go b/cmd/metal-api/internal/datastore/migrations/07_size_reservations_table.go new file mode 100644 index 000000000..ddbe86266 --- /dev/null +++ b/cmd/metal-api/internal/datastore/migrations/07_size_reservations_table.go @@ -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 + }, + }) +} diff --git a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go index 9e726347d..07be572cc 100644 --- a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go +++ b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go @@ -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" @@ -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) @@ -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()) diff --git a/cmd/metal-api/internal/datastore/rethinkdb.go b/cmd/metal-api/internal/datastore/rethinkdb.go index 38cbc07ca..ea21a0ff9 100644 --- a/cmd/metal-api/internal/datastore/rethinkdb.go +++ b/cmd/metal-api/internal/datastore/rethinkdb.go @@ -31,6 +31,7 @@ var tables = []string{ "sharedmutex", "size", "sizeimageconstraint", + "sizereservation", "switch", "switchstatus", VRFIntegerPool.String(), VRFIntegerPool.String() + "info", @@ -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 { @@ -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 diff --git a/cmd/metal-api/internal/datastore/shared_mutex.go b/cmd/metal-api/internal/datastore/shared_mutex.go index 47007d1ec..f34220b30 100644 --- a/cmd/metal-api/internal/datastore/shared_mutex.go +++ b/cmd/metal-api/internal/datastore/shared_mutex.go @@ -76,10 +76,6 @@ type lockOptAcquireTimeout struct { timeout time.Duration } -func newLockOptAcquireTimeout(t time.Duration) *lockOptAcquireTimeout { - return &lockOptAcquireTimeout{timeout: t} -} - func (m *sharedMutex) lock(ctx context.Context, key string, expiration time.Duration, opts ...lockOpt) error { timeout := defaultSharedMutexAcquireTimeout for _, opt := range opts { diff --git a/cmd/metal-api/internal/datastore/shared_mutex_test.go b/cmd/metal-api/internal/datastore/shared_mutex_test.go index 888685e61..3dd620b3a 100644 --- a/cmd/metal-api/internal/datastore/shared_mutex_test.go +++ b/cmd/metal-api/internal/datastore/shared_mutex_test.go @@ -16,6 +16,10 @@ import ( r "gopkg.in/rethinkdb/rethinkdb-go.v6" ) +func newLockOptAcquireTimeout(t time.Duration) *lockOptAcquireTimeout { + return &lockOptAcquireTimeout{timeout: t} +} + func Test_sharedMutex_reallyLocking(t *testing.T) { defer mutexCleanup(t) ctx := context.Background() diff --git a/cmd/metal-api/internal/datastore/size_integration_test.go b/cmd/metal-api/internal/datastore/size_integration_test.go index c774a92f5..ccd3cba16 100644 --- a/cmd/metal-api/internal/datastore/size_integration_test.go +++ b/cmd/metal-api/internal/datastore/size_integration_test.go @@ -62,14 +62,6 @@ func (_ *sizeTestable) defaultBody(s *metal.Size) *metal.Size { if s.Constraints == nil { s.Constraints = []metal.Constraint{} } - if s.Reservations == nil { - s.Reservations = metal.Reservations{} - } - for i := range s.Reservations { - if s.Reservations[i].PartitionIDs == nil { - s.Reservations[i].PartitionIDs = []string{} - } - } return s } @@ -152,41 +144,6 @@ func TestRethinkStore_SearchSizes(t *testing.T) { }, wantErr: nil, }, - { - name: "search reservation project", - q: &SizeSearchQuery{ - Reservation: Reservation{ - Project: pointer.Pointer("2"), - }, - }, - mock: []*metal.Size{ - {Base: metal.Base{ID: "1"}, Reservations: metal.Reservations{{ProjectID: "1"}}}, - {Base: metal.Base{ID: "2"}, Reservations: metal.Reservations{{ProjectID: "2"}}}, - {Base: metal.Base{ID: "3"}, Reservations: metal.Reservations{{ProjectID: "3"}}}, - }, - want: []*metal.Size{ - tt.defaultBody(&metal.Size{Base: metal.Base{ID: "2"}, Reservations: metal.Reservations{{ProjectID: "2"}}}), - }, - wantErr: nil, - }, - { - name: "search reservation partition", - q: &SizeSearchQuery{ - Reservation: Reservation{ - Partition: pointer.Pointer("p1"), - }, - }, - mock: []*metal.Size{ - {Base: metal.Base{ID: "1"}, Reservations: metal.Reservations{{PartitionIDs: []string{"p1"}}}}, - {Base: metal.Base{ID: "2"}, Reservations: metal.Reservations{{PartitionIDs: []string{"p1", "p2"}}}}, - {Base: metal.Base{ID: "3"}, Reservations: metal.Reservations{{PartitionIDs: []string{"p3"}}}}, - }, - want: []*metal.Size{ - tt.defaultBody(&metal.Size{Base: metal.Base{ID: "1"}, Reservations: metal.Reservations{{PartitionIDs: []string{"p1"}}}}), - tt.defaultBody(&metal.Size{Base: metal.Base{ID: "2"}, Reservations: metal.Reservations{{PartitionIDs: []string{"p1", "p2"}}}}), - }, - wantErr: nil, - }, } for i := range tests { diff --git a/cmd/metal-api/internal/datastore/size_reservation.go b/cmd/metal-api/internal/datastore/size_reservation.go new file mode 100644 index 000000000..52bea5058 --- /dev/null +++ b/cmd/metal-api/internal/datastore/size_reservation.go @@ -0,0 +1,91 @@ +package datastore + +import ( + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" + r "gopkg.in/rethinkdb/rethinkdb-go.v6" +) + +// SizeReservationSearchQuery can be used to search sizes. +type SizeReservationSearchQuery struct { + ID *string `json:"id" optional:"true"` + SizeID *string `json:"sizeid" optional:"true"` + Name *string `json:"name" optional:"true"` + Labels map[string]string `json:"labels" optional:"true"` + Partition *string `json:"partition" optional:"true"` + Project *string `json:"project" optional:"true"` +} + +func (s *SizeReservationSearchQuery) generateTerm(rs *RethinkStore) *r.Term { + q := *rs.sizeReservationTable() + + if s.ID != nil { + q = q.Filter(func(row r.Term) r.Term { + return row.Field("id").Eq(*s.ID) + }) + } + + if s.SizeID != nil { + q = q.Filter(func(row r.Term) r.Term { + return row.Field("sizeid").Eq(*s.SizeID) + }) + } + + if s.Name != nil { + q = q.Filter(func(row r.Term) r.Term { + return row.Field("name").Eq(*s.Name) + }) + } + + for k, v := range s.Labels { + k := k + v := v + q = q.Filter(func(row r.Term) r.Term { + return row.Field("labels").Field(k).Eq(v) + }) + } + + if s.Project != nil { + q = q.Filter(func(row r.Term) r.Term { + return row.Field("projectid").Eq(*s.Project) + }) + } + + if s.Partition != nil { + q = q.Filter(func(row r.Term) r.Term { + return row.Field("partitionids").Contains(r.Expr(*s.Partition)) + }) + } + + return &q +} + +func (rs *RethinkStore) FindSizeReservation(id string) (*metal.SizeReservation, error) { + var s metal.SizeReservation + err := rs.findEntityByID(rs.sizeReservationTable(), &s, id) + if err != nil { + return nil, err + } + return &s, nil +} + +func (rs *RethinkStore) SearchSizeReservations(q *SizeReservationSearchQuery, rvs *metal.SizeReservations) error { + return rs.searchEntities(q.generateTerm(rs), rvs) +} + +func (rs *RethinkStore) ListSizeReservations() (metal.SizeReservations, error) { + szs := make(metal.SizeReservations, 0) + err := rs.listEntities(rs.sizeReservationTable(), &szs) + return szs, err +} + +func (rs *RethinkStore) CreateSizeReservation(rv *metal.SizeReservation) error { + return rs.createEntity(rs.sizeReservationTable(), rv) +} + +func (rs *RethinkStore) DeleteSizeReservation(rv *metal.SizeReservation) error { + return rs.deleteEntity(rs.sizeReservationTable(), rv) +} + +func (rs *RethinkStore) UpdateSizeReservation(oldRv *metal.SizeReservation, newRv *metal.SizeReservation) error { + return rs.updateEntity(rs.sizeReservationTable(), newRv, oldRv) +} diff --git a/cmd/metal-api/internal/datastore/size_reservation_integration_test.go b/cmd/metal-api/internal/datastore/size_reservation_integration_test.go new file mode 100644 index 000000000..54e1f1eb5 --- /dev/null +++ b/cmd/metal-api/internal/datastore/size_reservation_integration_test.go @@ -0,0 +1,336 @@ +//go:build integration +// +build integration + +package datastore + +import ( + "testing" + + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" + "github.com/metal-stack/metal-lib/pkg/pointer" + "github.com/stretchr/testify/require" +) + +type sizeReservationTestable struct{} + +func (_ *sizeReservationTestable) wipe() error { + _, err := sharedDS.sizeReservationTable().Delete().RunWrite(sharedDS.session) + return err +} + +func (_ *sizeReservationTestable) create(s *metal.SizeReservation) error { // nolint:unused + return sharedDS.CreateSizeReservation(s) +} + +func (_ *sizeReservationTestable) delete(id string) error { // nolint:unused + return sharedDS.DeleteSizeReservation(&metal.SizeReservation{Base: metal.Base{ID: id}}) +} + +func (_ *sizeReservationTestable) update(old *metal.SizeReservation, mutateFn func(s *metal.SizeReservation)) error { // nolint:unused + mod := *old + if mutateFn != nil { + mutateFn(&mod) + } + + return sharedDS.UpdateSizeReservation(old, &mod) +} + +func (_ *sizeReservationTestable) find(id string) (*metal.SizeReservation, error) { // nolint:unused + return sharedDS.FindSizeReservation(id) +} + +func (_ *sizeReservationTestable) list() ([]*metal.SizeReservation, error) { // nolint:unused + res, err := sharedDS.ListSizeReservations() + if err != nil { + return nil, err + } + + return derefSlice(res), nil +} + +func (_ *sizeReservationTestable) search(q *SizeReservationSearchQuery) ([]*metal.SizeReservation, error) { // nolint:unused + var res metal.SizeReservations + err := sharedDS.SearchSizeReservations(q, &res) + if err != nil { + return nil, err + } + + return derefSlice(res), nil +} + +func (_ *sizeReservationTestable) defaultBody(s *metal.SizeReservation) *metal.SizeReservation { + if s.PartitionIDs == nil { + s.PartitionIDs = []string{} + } + return s +} + +func TestRethinkStore_FindSizeReservation(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []findTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "find", + id: "2", + + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + want: tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}}), + wantErr: nil, + }, + { + name: "not found", + id: "4", + want: nil, + wantErr: metal.NotFound(`no sizereservation with id "4" found`), + }, + } + for i := range tests { + tests[i].run(t, tt) + } +} + +func TestRethinkStore_SearchSizeReservations(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []searchTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "empty result", + q: &SizeReservationSearchQuery{ + ID: pointer.Pointer("2"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + }, + want: nil, + wantErr: nil, + }, + { + name: "search by id", + q: &SizeReservationSearchQuery{ + ID: pointer.Pointer("2"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}}), + }, + wantErr: nil, + }, + { + name: "search by name", + q: &SizeReservationSearchQuery{ + Name: pointer.Pointer("b"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1", Name: "a"}}, + {Base: metal.Base{ID: "2", Name: "b"}}, + {Base: metal.Base{ID: "3", Name: "c"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2", Name: "b"}}), + }, + wantErr: nil, + }, + { + name: "search by size", + q: &SizeReservationSearchQuery{ + SizeID: pointer.Pointer("size-a"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}, SizeID: "size-a"}, + {Base: metal.Base{ID: "2"}, SizeID: "size-b"}, + {Base: metal.Base{ID: "3"}, SizeID: "size-c"}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}, SizeID: "size-a"}), + }, + wantErr: nil, + }, + { + name: "search by label", + q: &SizeReservationSearchQuery{ + Labels: map[string]string{ + "a": "b", + }, + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}, Labels: map[string]string{"a": "x"}}, + {Base: metal.Base{ID: "2"}, Labels: map[string]string{"a": "b"}}, + {Base: metal.Base{ID: "3"}, Labels: map[string]string{"a": "b"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}, Labels: map[string]string{"a": "b"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "3"}, Labels: map[string]string{"a": "b"}}), + }, + wantErr: nil, + }, + { + name: "search by partition", + q: &SizeReservationSearchQuery{ + Partition: pointer.Pointer("b"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}, PartitionIDs: []string{"b"}}, + {Base: metal.Base{ID: "2"}, PartitionIDs: []string{"a", "b"}}, + {Base: metal.Base{ID: "3"}, PartitionIDs: []string{"a"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}, PartitionIDs: []string{"b"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}, PartitionIDs: []string{"a", "b"}}), + }, + wantErr: nil, + }, + { + name: "search by project", + q: &SizeReservationSearchQuery{ + Project: pointer.Pointer("3"), + }, + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}, ProjectID: "1"}, + {Base: metal.Base{ID: "2"}, ProjectID: "2"}, + {Base: metal.Base{ID: "3"}, ProjectID: "3"}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "3"}, ProjectID: "3"}), + }, + wantErr: nil, + }, + } + + for i := range tests { + tests[i].run(t, tt) + } +} + +func TestRethinkStore_ListSizeReservations(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []listTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "list", + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "3"}}), + }, + }, + } + for i := range tests { + tests[i].run(t, tt) + } +} + +func TestRethinkStore_CreateSizeReservation(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []createTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "create", + want: tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}}), + wantErr: nil, + }, + { + name: "already exists", + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + }, + want: tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}}), + wantErr: metal.Conflict(`cannot create sizereservation in database, entity already exists: 1`), + }, + } + for i := range tests { + tests[i].run(t, tt) + } +} + +func TestRethinkStore_DeleteSizeReservation(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []deleteTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "delete", + id: "2", + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "3"}}), + }, + }, + { + name: "not exists results in noop", + id: "abc", + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + want: []*metal.SizeReservation{ + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "1"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "2"}}), + tt.defaultBody(&metal.SizeReservation{Base: metal.Base{ID: "3"}}), + }, + }, + } + for i := range tests { + tests[i].run(t, tt) + } +} + +func TestRethinkStore_UpdateSizeReservation(t *testing.T) { + tt := &sizeReservationTestable{} + defer func() { + require.NoError(t, tt.wipe()) + }() + + tests := []updateTest[*metal.SizeReservation, *SizeReservationSearchQuery]{ + { + name: "update", + mock: []*metal.SizeReservation{ + {Base: metal.Base{ID: "1"}}, + {Base: metal.Base{ID: "2"}}, + {Base: metal.Base{ID: "3"}}, + }, + mutateFn: func(s *metal.SizeReservation) { + s.Labels = map[string]string{"a": "b"} + }, + want: tt.defaultBody(&metal.SizeReservation{ + Base: metal.Base{ID: "1"}, + Labels: map[string]string{"a": "b"}, + }), + }, + } + for i := range tests { + tests[i].run(t, tt) + } +} diff --git a/cmd/metal-api/internal/grpc/boot-service_test.go b/cmd/metal-api/internal/grpc/boot-service_test.go index b0132ca8e..147a249dc 100644 --- a/cmd/metal-api/internal/grpc/boot-service_test.go +++ b/cmd/metal-api/internal/grpc/boot-service_test.go @@ -40,7 +40,7 @@ func TestBootService_Register(t *testing.T) { name string uuid string numcores int - memory int + memory uint64 dbsizes []metal.Size dbmachines metal.Machines neighbormac1 metal.MacAddress @@ -121,7 +121,7 @@ func TestBootService_Register(t *testing.T) { req := &v1.BootServiceRegisterRequest{ Uuid: tt.uuid, Hardware: &v1.MachineHardware{ - Memory: uint64(tt.memory), + Memory: tt.memory, Disks: []*v1.MachineBlockDevice{ { Size: 1000000000000, diff --git a/cmd/metal-api/internal/headscale/client.go b/cmd/metal-api/internal/headscale/client.go index 500fd11c6..17ee86d39 100644 --- a/cmd/metal-api/internal/headscale/client.go +++ b/cmd/metal-api/internal/headscale/client.go @@ -8,7 +8,7 @@ import ( "time" headscalev1 "github.com/juanfont/headscale/gen/go/headscale/v1" - "github.com/juanfont/headscale/hscontrol" + "github.com/juanfont/headscale/hscontrol/db" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" timestamppb "google.golang.org/protobuf/types/known/timestamppb" @@ -90,7 +90,7 @@ func (h *HeadscaleClient) CreateUser(ctx context.Context, name string) error { } _, err := h.client.CreateUser(ctx, req) // TODO: this error check is pretty rough, but it's not easily possible to compare the proto error directly :/ - if err != nil && !strings.Contains(err.Error(), hscontrol.ErrUserExists.Error()) { + if err != nil && !strings.Contains(err.Error(), db.ErrUserExists.Error()) { return fmt.Errorf("failed to create new VPN user: %w", err) } @@ -111,42 +111,42 @@ func (h *HeadscaleClient) CreatePreAuthKey(ctx context.Context, user string, exp return resp.PreAuthKey.Key, nil } -func (h *HeadscaleClient) MachinesConnected(ctx context.Context) ([]*headscalev1.Machine, error) { - resp, err := h.client.ListMachines(ctx, &headscalev1.ListMachinesRequest{}) +func (h *HeadscaleClient) NodesConnected(ctx context.Context) ([]*headscalev1.Node, error) { + resp, err := h.client.ListNodes(ctx, &headscalev1.ListNodesRequest{}) if err != nil || resp == nil { return nil, fmt.Errorf("failed to list machines: %w", err) } - return resp.Machines, nil + return resp.Nodes, nil } -// DeleteMachine removes the node entry from headscale DB -func (h *HeadscaleClient) DeleteMachine(ctx context.Context, machineID, projectID string) (err error) { - machine, err := h.getMachine(ctx, machineID, projectID) +// DeleteNode removes the node entry from headscale DB +func (h *HeadscaleClient) DeleteNode(ctx context.Context, machineID, projectID string) (err error) { + machine, err := h.getNode(ctx, machineID, projectID) if err != nil || machine == nil { return err } - req := &headscalev1.DeleteMachineRequest{ - MachineId: machine.Id, + req := &headscalev1.DeleteNodeRequest{ + NodeId: machine.Id, } - if _, err := h.client.DeleteMachine(ctx, req); err != nil { + if _, err := h.client.DeleteNode(ctx, req); err != nil { return fmt.Errorf("failed to delete machine: %w", err) } return nil } -func (h *HeadscaleClient) getMachine(ctx context.Context, machineID, projectID string) (machine *headscalev1.Machine, err error) { - req := &headscalev1.ListMachinesRequest{ +func (h *HeadscaleClient) getNode(ctx context.Context, machineID, projectID string) (machine *headscalev1.Node, err error) { + req := &headscalev1.ListNodesRequest{ User: projectID, } - resp, err := h.client.ListMachines(ctx, req) + resp, err := h.client.ListNodes(ctx, req) if err != nil || resp == nil { return nil, fmt.Errorf("failed to list machines: %w", err) } - for _, m := range resp.Machines { + for _, m := range resp.Nodes { if m.Name == machineID { return m, nil } diff --git a/cmd/metal-api/internal/issues/types.go b/cmd/metal-api/internal/issues/types.go index db05f05b1..2ff13ad0e 100644 --- a/cmd/metal-api/internal/issues/types.go +++ b/cmd/metal-api/internal/issues/types.go @@ -24,18 +24,6 @@ func AllIssueTypes() []Type { } } -func NotAllocatableIssueTypes() []Type { - return []Type{ - TypeNoPartition, - TypeLivelinessDead, - TypeLivelinessUnknown, - TypeLivelinessNotAvailable, - TypeFailedMachineReclaim, - TypeCrashLoop, - TypeNoEventContainer, - } -} - func NewIssueFromType(t Type) (issue, error) { switch t { case TypeNoPartition: diff --git a/cmd/metal-api/internal/metal/machine.go b/cmd/metal-api/internal/metal/machine.go index 8ce443c88..865973551 100644 --- a/cmd/metal-api/internal/metal/machine.go +++ b/cmd/metal-api/internal/metal/machine.go @@ -566,16 +566,17 @@ type Fru struct { // IPMI connection data type IPMI struct { // Address is host:port of the connection to the ipmi BMC, host can be either a ip address or a hostname - Address string `rethinkdb:"address" json:"address"` - MacAddress string `rethinkdb:"mac" json:"mac"` - User string `rethinkdb:"user" json:"user"` - Password string `rethinkdb:"password" json:"password"` - Interface string `rethinkdb:"interface" json:"interface"` - Fru Fru `rethinkdb:"fru" json:"fru"` - BMCVersion string `rethinkdb:"bmcversion" json:"bmcversion"` - PowerState string `rethinkdb:"powerstate" json:"powerstate"` - PowerMetric *PowerMetric `rethinkdb:"powermetric" json:"powermetric"` - LastUpdated time.Time `rethinkdb:"last_updated" json:"last_updated"` + Address string `rethinkdb:"address" json:"address"` + MacAddress string `rethinkdb:"mac" json:"mac"` + User string `rethinkdb:"user" json:"user"` + Password string `rethinkdb:"password" json:"password"` + Interface string `rethinkdb:"interface" json:"interface"` + Fru Fru `rethinkdb:"fru" json:"fru"` + BMCVersion string `rethinkdb:"bmcversion" json:"bmcversion"` + PowerState string `rethinkdb:"powerstate" json:"powerstate"` + PowerMetric *PowerMetric `rethinkdb:"powermetric" json:"powermetric"` + PowerSupplies PowerSupplies `rethinkdb:"powersupplies" json:"powersupplies"` + LastUpdated time.Time `rethinkdb:"last_updated" json:"last_updated"` } type PowerMetric struct { @@ -598,6 +599,17 @@ type PowerMetric struct { MinConsumedWatts float32 `rethinkdb:"minconsumedwatts" json:"minconsumedwatts"` } +type PowerSupplies []PowerSupply +type PowerSupply struct { + // Status shall contain any status or health properties + // of the resource. + Status PowerSupplyStatus `rethinkdb:"status" json:"status"` +} +type PowerSupplyStatus struct { + Health string `rethinkdb:"health" json:"health"` + State string `rethinkdb:"state" json:"state"` +} + // BIOS contains machine bios information type BIOS struct { Version string `rethinkdb:"version" json:"version"` diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 6068311b1..117e07722 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -303,9 +303,12 @@ func (nics Nics) FilterByHostname(hostname string) (res Nics) { return res } +// NicMap maps nic names to the corresponding nics +type NicMap map[string]*Nic + // ByName creates a map (nic names --> nic) from a nic list. -func (nics Nics) ByName() map[string]*Nic { - res := make(map[string]*Nic) +func (nics Nics) ByName() NicMap { + res := make(NicMap) for i, n := range nics { res[n.Name] = &nics[i] @@ -315,8 +318,8 @@ func (nics Nics) ByName() map[string]*Nic { } // ByIdentifier creates a map (nic identifier --> nic) from a nic list. -func (nics Nics) ByIdentifier() map[string]*Nic { - res := make(map[string]*Nic) +func (nics Nics) ByIdentifier() NicMap { + res := make(NicMap) for i, n := range nics { res[n.GetIdentifier()] = &nics[i] diff --git a/cmd/metal-api/internal/metal/network_test.go b/cmd/metal-api/internal/metal/network_test.go index a7dcfddfe..e935bd74f 100644 --- a/cmd/metal-api/internal/metal/network_test.go +++ b/cmd/metal-api/internal/metal/network_test.go @@ -23,7 +23,7 @@ func TestNics_ByIdentifier(t *testing.T) { nicArray[i].Neighbors = append(nicArray[0:i], nicArray[i+1:countOfNics]...) } - map1 := map[string]*Nic{} + map1 := NicMap{} for i, n := range nicArray { map1[string(n.MacAddress)] = &nicArray[i] } @@ -31,7 +31,7 @@ func TestNics_ByIdentifier(t *testing.T) { tests := []struct { name string nics Nics - want map[string]*Nic + want NicMap }{ { name: "TestNics_ByIdentifier Test 1", diff --git a/cmd/metal-api/internal/metal/size.go b/cmd/metal-api/internal/metal/size.go index 13228b5bb..8449fa17e 100644 --- a/cmd/metal-api/internal/metal/size.go +++ b/cmd/metal-api/internal/metal/size.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "path/filepath" - "slices" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" "github.com/samber/lo" @@ -13,22 +12,10 @@ import ( // A Size represents a supported machine size. type Size struct { Base - Constraints []Constraint `rethinkdb:"constraints" json:"constraints"` - Reservations Reservations `rethinkdb:"reservations" json:"reservations"` - Labels map[string]string `rethinkdb:"labels" json:"labels"` + Constraints []Constraint `rethinkdb:"constraints" json:"constraints"` + Labels map[string]string `rethinkdb:"labels" json:"labels"` } -// Reservation defines a reservation of a size for machine allocations -type Reservation 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 Reservations []Reservation - // ConstraintType ... type ConstraintType string @@ -280,10 +267,6 @@ func (s *Size) Validate(partitions PartitionMap, projects map[string]*mdmv1.Proj } } - if err := s.Reservations.Validate(partitions, projects); err != nil { - errs = append(errs, fmt.Errorf("size reservations are invalid: %w", err)) - } - if len(errs) > 0 { return fmt.Errorf("size %q is invalid: %w", s.ID, errors.Join(errs...)) } @@ -304,70 +287,3 @@ func (s *Size) Overlaps(ss *Sizes) *Size { } return nil } - -func (rs *Reservations) ForPartition(partitionID string) Reservations { - if rs == nil { - return nil - } - - var result Reservations - for _, r := range *rs { - r := r - if slices.Contains(r.PartitionIDs, partitionID) { - result = append(result, r) - } - } - - return result -} - -func (rs *Reservations) ForProject(projectID string) Reservations { - if rs == nil { - return nil - } - - var result Reservations - for _, r := range *rs { - r := r - if r.ProjectID == projectID { - result = append(result, r) - } - } - - return result -} - -func (rs *Reservations) Validate(partitions PartitionMap, projects map[string]*mdmv1.Project) error { - if rs == nil { - return nil - } - - for _, r := range *rs { - if r.Amount <= 0 { - return fmt.Errorf("amount must be a positive integer") - } - - if len(r.PartitionIDs) == 0 { - return fmt.Errorf("at least one partition id must be specified") - } - ids := map[string]bool{} - for _, partition := range r.PartitionIDs { - ids[partition] = true - if _, ok := partitions[partition]; !ok { - return fmt.Errorf("partition must exist before creating a size reservation") - } - } - if len(ids) != len(r.PartitionIDs) { - return fmt.Errorf("partitions must not contain duplicates") - } - - if r.ProjectID == "" { - return fmt.Errorf("project id must be specified") - } - if _, ok := projects[r.ProjectID]; !ok { - return fmt.Errorf("project must exist before creating a size reservation") - } - } - - return nil -} diff --git a/cmd/metal-api/internal/metal/size_reservation.go b/cmd/metal-api/internal/metal/size_reservation.go new file mode 100644 index 000000000..8af03b35d --- /dev/null +++ b/cmd/metal-api/internal/metal/size_reservation.go @@ -0,0 +1,92 @@ +package metal + +import ( + "fmt" + "slices" + + mdmv1 "github.com/metal-stack/masterdata-api/api/v1" +) + +// SizeReservation defines a reservation of a size for machine allocations +type SizeReservation struct { + Base + SizeID string `rethinkdb:"sizeid" json:"sizeid"` + Amount int `rethinkdb:"amount" json:"amount"` + ProjectID string `rethinkdb:"projectid" json:"projectid"` + PartitionIDs []string `rethinkdb:"partitionids" json:"partitionids"` + Labels map[string]string `rethinkdb:"labels" json:"labels"` +} + +type SizeReservations []SizeReservation + +func (rs SizeReservations) BySize() map[string]SizeReservations { + res := map[string]SizeReservations{} + for _, rv := range rs { + res[rv.SizeID] = append(res[rv.SizeID], rv) + } + return res +} + +func (rs *SizeReservations) ForPartition(partitionID string) SizeReservations { + if rs == nil { + return nil + } + + var result SizeReservations + for _, r := range *rs { + r := r + if slices.Contains(r.PartitionIDs, partitionID) { + result = append(result, r) + } + } + + return result +} + +func (rs *SizeReservations) Validate(sizes SizeMap, partitions PartitionMap, projects map[string]*mdmv1.Project) error { + if rs == nil { + return nil + } + + for _, r := range *rs { + err := r.Validate(sizes, partitions, projects) + if err != nil { + return err + } + } + + return nil +} + +func (r *SizeReservation) Validate(sizes SizeMap, partitions PartitionMap, projects map[string]*mdmv1.Project) error { + if r.Amount <= 0 { + return fmt.Errorf("amount must be a positive integer") + } + + if _, ok := sizes[r.SizeID]; !ok { + return fmt.Errorf("size must exist before creating a size reservation") + } + + if len(r.PartitionIDs) == 0 { + return fmt.Errorf("at least one partition id must be specified") + } + ids := map[string]bool{} + for _, partition := range r.PartitionIDs { + ids[partition] = true + if _, ok := partitions[partition]; !ok { + return fmt.Errorf("partition must exist before creating a size reservation") + } + } + if len(ids) != len(r.PartitionIDs) { + return fmt.Errorf("partitions must not contain duplicates") + } + + if r.ProjectID == "" { + return fmt.Errorf("project id must be specified") + } + if _, ok := projects[r.ProjectID]; !ok { + return fmt.Errorf("project must exist before creating a size reservation") + } + + return nil +} diff --git a/cmd/metal-api/internal/metal/size_reservation_test.go b/cmd/metal-api/internal/metal/size_reservation_test.go new file mode 100644 index 000000000..8ab5458cd --- /dev/null +++ b/cmd/metal-api/internal/metal/size_reservation_test.go @@ -0,0 +1,282 @@ +package metal + +import ( + "fmt" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + mdmv1 "github.com/metal-stack/masterdata-api/api/v1" + "github.com/metal-stack/metal-lib/pkg/testcommon" +) + +func TestReservations_ForPartition(t *testing.T) { + tests := []struct { + name string + rs *SizeReservations + partitionID string + want SizeReservations + }{ + { + name: "nil", + rs: nil, + partitionID: "a", + want: nil, + }, + { + name: "correctly filtered", + rs: &SizeReservations{ + { + PartitionIDs: []string{"a", "b"}, + }, + { + PartitionIDs: []string{"c"}, + }, + { + PartitionIDs: []string{"a"}, + }, + }, + partitionID: "a", + want: SizeReservations{ + { + PartitionIDs: []string{"a", "b"}, + }, + { + PartitionIDs: []string{"a"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.rs.ForPartition(tt.partitionID); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reservations.ForPartition() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReservations_Validate(t *testing.T) { + tests := []struct { + name string + sizes SizeMap + partitions PartitionMap + projects map[string]*mdmv1.Project + rs *SizeReservations + wantErr error + }{ + { + name: "empty reservations", + sizes: nil, + partitions: nil, + projects: nil, + rs: nil, + wantErr: nil, + }, + { + name: "invalid amount", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: -3, + ProjectID: "3", + PartitionIDs: []string{"b"}, + }, + }, + wantErr: fmt.Errorf("amount must be a positive integer"), + }, + { + name: "size does not exist", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c2", + Amount: 3, + ProjectID: "3", + PartitionIDs: []string{"d"}, + }, + }, + wantErr: fmt.Errorf("size must exist before creating a size reservation"), + }, + { + name: "no partitions referenced", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + ProjectID: "3", + }, + }, + wantErr: fmt.Errorf("at least one partition id must be specified"), + }, + { + name: "partition does not exist", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + ProjectID: "3", + PartitionIDs: []string{"d"}, + }, + }, + wantErr: fmt.Errorf("partition must exist before creating a size reservation"), + }, + { + name: "partition duplicates", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + ProjectID: "3", + PartitionIDs: []string{"a", "b", "c", "b"}, + }, + }, + wantErr: fmt.Errorf("partitions must not contain duplicates"), + }, + { + name: "no project referenced", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + PartitionIDs: []string{"a"}, + }, + }, + wantErr: fmt.Errorf("project id must be specified"), + }, + { + name: "project does not exist", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + ProjectID: "4", + PartitionIDs: []string{"a"}, + }, + }, + wantErr: fmt.Errorf("project must exist before creating a size reservation"), + }, + { + name: "valid reservation", + sizes: SizeMap{ + "c1": Size{}, + }, + partitions: PartitionMap{ + "a": Partition{}, + "b": Partition{}, + "c": Partition{}, + }, + projects: map[string]*mdmv1.Project{ + "1": {}, + "2": {}, + "3": {}, + }, + rs: &SizeReservations{ + { + SizeID: "c1", + Amount: 3, + ProjectID: "2", + PartitionIDs: []string{"b", "c"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.rs.Validate(tt.sizes, tt.partitions, tt.projects) + if diff := cmp.Diff(tt.wantErr, err, testcommon.ErrorStringComparer()); diff != "" { + t.Errorf("error diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/cmd/metal-api/internal/metal/size_test.go b/cmd/metal-api/internal/metal/size_test.go index 97c4d4cd1..e565740e5 100644 --- a/cmd/metal-api/internal/metal/size_test.go +++ b/cmd/metal-api/internal/metal/size_test.go @@ -1,14 +1,11 @@ package metal import ( - "fmt" "reflect" "testing" "github.com/google/go-cmp/cmp" - mdmv1 "github.com/metal-stack/masterdata-api/api/v1" "github.com/metal-stack/metal-lib/pkg/pointer" - "github.com/metal-stack/metal-lib/pkg/testcommon" "github.com/stretchr/testify/require" ) @@ -1219,275 +1216,6 @@ func TestSize_Validate(t *testing.T) { } } -func TestReservations_ForPartition(t *testing.T) { - tests := []struct { - name string - rs *Reservations - partitionID string - want Reservations - }{ - { - name: "nil", - rs: nil, - partitionID: "a", - want: nil, - }, - { - name: "correctly filtered", - rs: &Reservations{ - { - PartitionIDs: []string{"a", "b"}, - }, - { - PartitionIDs: []string{"c"}, - }, - { - PartitionIDs: []string{"a"}, - }, - }, - partitionID: "a", - want: Reservations{ - { - PartitionIDs: []string{"a", "b"}, - }, - { - PartitionIDs: []string{"a"}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.rs.ForPartition(tt.partitionID); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Reservations.ForPartition() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestReservations_ForProject(t *testing.T) { - tests := []struct { - name string - rs *Reservations - projectID string - want Reservations - }{ - { - name: "nil", - rs: nil, - projectID: "a", - want: nil, - }, - { - name: "correctly filtered", - rs: &Reservations{ - { - ProjectID: "a", - }, - { - ProjectID: "c", - }, - { - ProjectID: "a", - }, - }, - projectID: "a", - want: Reservations{ - { - ProjectID: "a", - }, - { - ProjectID: "a", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.rs.ForProject(tt.projectID); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Reservations.ForProject() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestReservations_Validate(t *testing.T) { - tests := []struct { - name string - partitions PartitionMap - projects map[string]*mdmv1.Project - rs *Reservations - wantErr error - }{ - { - name: "empty reservations", - partitions: nil, - projects: nil, - rs: nil, - wantErr: nil, - }, - { - name: "invalid amount", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: -3, - Description: "test", - ProjectID: "3", - PartitionIDs: []string{"b"}, - }, - }, - wantErr: fmt.Errorf("amount must be a positive integer"), - }, - { - name: "no partitions referenced", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - ProjectID: "3", - }, - }, - wantErr: fmt.Errorf("at least one partition id must be specified"), - }, - { - name: "partition does not exist", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - ProjectID: "3", - PartitionIDs: []string{"d"}, - }, - }, - wantErr: fmt.Errorf("partition must exist before creating a size reservation"), - }, - { - name: "partition duplicates", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - ProjectID: "3", - PartitionIDs: []string{"a", "b", "c", "b"}, - }, - }, - wantErr: fmt.Errorf("partitions must not contain duplicates"), - }, - { - name: "no project referenced", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - PartitionIDs: []string{"a"}, - }, - }, - wantErr: fmt.Errorf("project id must be specified"), - }, - { - name: "project does not exist", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - ProjectID: "4", - PartitionIDs: []string{"a"}, - }, - }, - wantErr: fmt.Errorf("project must exist before creating a size reservation"), - }, - { - name: "valid reservation", - partitions: PartitionMap{ - "a": Partition{}, - "b": Partition{}, - "c": Partition{}, - }, - projects: map[string]*mdmv1.Project{ - "1": {}, - "2": {}, - "3": {}, - }, - rs: &Reservations{ - { - Amount: 3, - Description: "test", - ProjectID: "2", - PartitionIDs: []string{"b", "c"}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.rs.Validate(tt.partitions, tt.projects) - if diff := cmp.Diff(tt.wantErr, err, testcommon.ErrorStringComparer()); diff != "" { - t.Errorf("error diff (-want +got):\n%s", diff) - } - }) - } -} - func TestConstraint_overlaps(t *testing.T) { tests := []struct { name string diff --git a/cmd/metal-api/internal/service/async-actor.go b/cmd/metal-api/internal/service/async-actor.go index 507f509ec..a7f775fc2 100644 --- a/cmd/metal-api/internal/service/async-actor.go +++ b/cmd/metal-api/internal/service/async-actor.go @@ -49,7 +49,7 @@ func (a *asyncActor) freeMachine(ctx context.Context, pub bus.Publisher, m *meta if headscaleClient != nil && m.Allocation != nil { // always call DeleteMachine, in case machine is not registered it will return nil - if err := headscaleClient.DeleteMachine(ctx, m.ID, m.Allocation.Project); err != nil { + if err := headscaleClient.DeleteNode(ctx, m.ID, m.Allocation.Project); err != nil { logger.Error("unable to delete Node entry from headscale DB", "machineID", m.ID, "error", err) } } diff --git a/cmd/metal-api/internal/service/machine-service.go b/cmd/metal-api/internal/service/machine-service.go index e20b48fbe..4c85c96a0 100644 --- a/cmd/metal-api/internal/service/machine-service.go +++ b/cmd/metal-api/internal/service/machine-service.go @@ -930,6 +930,16 @@ func (r *machineResource) ipmiReport(request *restful.Request, response *restful MinConsumedWatts: report.PowerMetric.MinConsumedWatts, } } + var powerSupplies metal.PowerSupplies + for _, ps := range report.PowerSupplies { + powerSupplies = append(powerSupplies, metal.PowerSupply{ + Status: metal.PowerSupplyStatus{ + Health: ps.Status.Health, + State: ps.Status.State, + }, + }) + } + newMachine.IPMI.PowerSupplies = powerSupplies ledstate, err := metal.LEDStateFrom(report.IndicatorLEDState) if err == nil { diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index b7fe1ca6b..5f5dfd7ce 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -375,10 +375,15 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque return nil, fmt.Errorf("unable to list sizes: %w", err) } + sizeReservations, err := r.ds.ListSizeReservations() + if err != nil { + return nil, fmt.Errorf("unable to list size reservations: %w", err) + } + machinesWithIssues, err := issues.Find(&issues.Config{ Machines: ms, EventContainers: ecs, - Only: issues.NotAllocatableIssueTypes(), + Omit: []issues.Type{issues.TypeLastEventError}, }) if err != nil { return nil, fmt.Errorf("unable to calculate machine issues: %w", err) @@ -389,6 +394,7 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque ecsByID = ecs.ByID() sizesByID = sizes.ByID() machinesByProject = ms.ByProjectID() + rvsBySize = sizeReservations.BySize() ) for _, m := range ms { @@ -436,24 +442,33 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque cap.Total++ - if m.Allocation != nil { - cap.Allocated++ - continue - } - if _, ok := machinesWithIssues[m.ID]; ok { cap.Faulty++ cap.FaultyMachines = append(cap.FaultyMachines, m.ID) - continue } - if m.State.Value == metal.AvailableState && metal.ProvisioningEventWaiting == pointer.FirstOrZero(ec.Events).Event { + // allocation dependent counts + switch { + case m.Allocation != nil: + cap.Allocated++ + case m.Waiting && !m.PreAllocated && m.State.Value == metal.AvailableState && ec.Liveliness == metal.MachineLivelinessAlive: + // the free and allocatable machine counts consider the same aspects as the query for electing the machine candidate! + cap.Allocatable++ cap.Free++ - continue + default: + cap.Unavailable++ } - cap.Other++ - cap.OtherMachines = append(cap.OtherMachines, m.ID) + // provisioning state dependent counts + switch pointer.FirstOrZero(ec.Events).Event { //nolint:exhaustive + case metal.ProvisioningEventPhonedHome: + cap.PhonedHome++ + case metal.ProvisioningEventWaiting: + cap.Waiting++ + default: + cap.Other++ + cap.OtherMachines = append(cap.OtherMachines, m.ID) + } } res := []v1.PartitionCapacity{} @@ -461,18 +476,27 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque pc := pc for _, cap := range pc.ServerCapacities { - cap := cap - size := sizesByID[cap.Size] - for _, reservation := range size.Reservations.ForPartition(pc.ID) { - reservation := reservation + rvs, ok := rvsBySize[size.ID] + if !ok { + continue + } + + for _, reservation := range rvs.ForPartition(pc.ID) { + usedReservations := min(len(machinesByProject[reservation.ProjectID].WithSize(size.ID).WithPartition(pc.ID)), reservation.Amount) cap.Reservations += reservation.Amount - cap.UsedReservations += min(len(machinesByProject[reservation.ProjectID].WithSize(size.ID).WithPartition(pc.ID)), reservation.Amount) + cap.UsedReservations += usedReservations + cap.Free -= reservation.Amount - usedReservations + cap.Free = max(cap.Free, 0) } } + for _, cap := range pc.ServerCapacities { + cap.RemainingReservations = cap.Reservations - cap.UsedReservations + } + res = append(res, *pc) } diff --git a/cmd/metal-api/internal/service/partition-service_test.go b/cmd/metal-api/internal/service/partition-service_test.go index a54d80ce7..06a032757 100644 --- a/cmd/metal-api/internal/service/partition-service_test.go +++ b/cmd/metal-api/internal/service/partition-service_test.go @@ -7,8 +7,11 @@ import ( "log/slog" "net/http" "net/http/httptest" + "slices" "testing" + "time" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" r "gopkg.in/rethinkdb/rethinkdb-go.v6" @@ -18,6 +21,7 @@ import ( v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" "github.com/metal-stack/metal-lib/httperrors" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/stretchr/testify/require" ) @@ -245,49 +249,496 @@ func TestUpdatePartition(t *testing.T) { } func TestPartitionCapacity(t *testing.T) { - ds, mock := datastore.InitMockDB(t) + var ( + mockMachines = func(mock *r.Mock, liveliness metal.MachineLiveliness, reservations metal.SizeReservations, ms ...metal.Machine) { + var ( + sizes metal.Sizes + events metal.ProvisioningEventContainers + partitions metal.Partitions + ) + + for _, m := range ms { + ec := metal.ProvisioningEventContainer{Base: metal.Base{ID: m.ID}, Liveliness: liveliness} + if m.Waiting { + ec.Events = append(ec.Events, metal.ProvisioningEvent{ + Event: metal.ProvisioningEventWaiting, + }) + } + if m.Allocation != nil { + ec.Events = append(ec.Events, metal.ProvisioningEvent{ + Event: metal.ProvisioningEventPhonedHome, + }) + } + events = append(events, ec) + if !slices.ContainsFunc(sizes, func(s metal.Size) bool { + return s.ID == m.SizeID + }) { + s := metal.Size{Base: metal.Base{ID: m.SizeID}} + sizes = append(sizes, s) + } + if !slices.ContainsFunc(partitions, func(p metal.Partition) bool { + return p.ID == m.PartitionID + }) { + partitions = append(partitions, metal.Partition{Base: metal.Base{ID: m.PartitionID}}) + } + } + + mock.On(r.DB("mockdb").Table("sizereservation")).Return(reservations, nil) + mock.On(r.DB("mockdb").Table("machine")).Return(ms, nil) + mock.On(r.DB("mockdb").Table("event")).Return(events, nil) + mock.On(r.DB("mockdb").Table("partition")).Return(partitions, nil) + mock.On(r.DB("mockdb").Table("size")).Return(sizes, nil) + } + + machineTpl = func(id, partition, size, project string) metal.Machine { + m := metal.Machine{ + Base: metal.Base{ID: id}, + PartitionID: partition, + SizeID: size, + IPMI: metal.IPMI{ // required for healthy machine state + Address: "1.2.3." + id, + MacAddress: "aa:bb:0" + id, + LastUpdated: time.Now().Add(-1 * time.Minute), + }, + State: metal.MachineState{ + Value: metal.AvailableState, + }, + } + if project != "" { + m.Allocation = &metal.MachineAllocation{ + Project: project, + } + } + return m + } + ) + + tests := []struct { + name string + mockFn func(mock *r.Mock) + want []*v1.PartitionCapacity + }{ + { + name: "one allocated machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + PhonedHome: 1, + Allocated: 1, + }, + }, + }, + }, + }, + { + name: "two allocated machines", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + m2 := machineTpl("2", "partition-a", "size-a", "project-123") + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1, m2) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 2, + PhonedHome: 2, + Allocated: 2, + }, + }, + }, + }, + }, + { + name: "one faulty, allocated machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + m1.IPMI.Address = "" + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + PhonedHome: 1, + Faulty: 1, + Allocated: 1, + FaultyMachines: []string{"1"}, + }, + }, + }, + }, + }, + { + name: "one waiting machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 1, + Allocatable: 1, + }, + }, + }, + }, + }, + { + name: "one dead machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true - ecs := []metal.ProvisioningEventContainer{} - for _, m := range testdata.TestMachines { - m := m - ecs = append(ecs, metal.ProvisioningEventContainer{ - Base: m.Base, + mockMachines(mock, metal.MachineLivelinessDead, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Faulty: 1, + Unavailable: 1, + FaultyMachines: []string{"1"}, + }, + }, + }, + }, + }, + { + name: "one waiting, one allocated machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + m2 := machineTpl("2", "partition-a", "size-a", "project-123") + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1, m2) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 2, + Allocated: 1, + Waiting: 1, + PhonedHome: 1, + Free: 1, + Allocatable: 1, + }, + }, + }, + }, + }, + { + name: "one free machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + m1.State.Value = metal.AvailableState + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 1, + Allocatable: 1, + }, + }, + }, + }, + }, + { + name: "one machine rebooting", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = false + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Other: 1, + Unavailable: 1, + OtherMachines: []string{"1"}, + }, + }, + }, + }, + }, + { + name: "reserved machine does not count as free", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + + reservations := []metal.SizeReservation{ + { + SizeID: "size-a", + Amount: 1, + ProjectID: "project-123", + PartitionIDs: []string{"partition-a"}, + }, + } + + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 0, + Allocatable: 1, + Reservations: 1, + UsedReservations: 0, + RemainingReservations: 1, + }, + }, + }, + }, + }, + { + name: "overbooked partition, free count capped at 0", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + + reservations := []metal.SizeReservation{ + { + SizeID: "size-a", + Amount: 1, + ProjectID: "project-123", + PartitionIDs: []string{"partition-a"}, + }, + { + SizeID: "size-a", + Amount: 2, + ProjectID: "project-456", + PartitionIDs: []string{"partition-a"}, + }, + } + + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 0, + Allocatable: 1, + Reservations: 3, + UsedReservations: 0, + RemainingReservations: 3, + }, + }, + }, + }, + }, + { + name: "reservations already used up (edge)", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + m2 := machineTpl("2", "partition-a", "size-a", "project-123") + m3 := machineTpl("3", "partition-a", "size-a", "") + m3.Waiting = true + + reservations := []metal.SizeReservation{ + { + SizeID: "size-a", + Amount: 2, + ProjectID: "project-123", + PartitionIDs: []string{"partition-a"}, + }, + } + + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 3, + Allocated: 2, + Waiting: 1, + Free: 1, + Allocatable: 1, + Reservations: 2, + UsedReservations: 2, + PhonedHome: 2, + }, + }, + }, + }, + }, + { + name: "reservations already used up", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + m2 := machineTpl("2", "partition-a", "size-a", "project-123") + m3 := machineTpl("3", "partition-a", "size-a", "") + m3.Waiting = true + + reservations := []metal.SizeReservation{ + { + SizeID: "size-a", + Amount: 1, + ProjectID: "project-123", + PartitionIDs: []string{"partition-a"}, + }, + } + + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 3, + Allocated: 2, + Waiting: 1, + Free: 1, + Allocatable: 1, + Reservations: 1, + UsedReservations: 1, + PhonedHome: 2, + }, + }, + }, + }, + }, + { + name: "other partition size reservation has no influence", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "project-123") + m2 := machineTpl("2", "partition-a", "size-a", "project-123") + m3 := machineTpl("3", "partition-a", "size-a", "") + m3.Waiting = true + + reservations := []metal.SizeReservation{ + { + SizeID: "size-a", + Amount: 2, + ProjectID: "project-123", + PartitionIDs: []string{"partition-a"}, + }, + { + SizeID: "size-a", + Amount: 2, + ProjectID: "project-123", + PartitionIDs: []string{"partition-b"}, + }, + } + + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 3, + Allocated: 2, + Waiting: 1, + Free: 1, + Allocatable: 1, + Reservations: 2, + UsedReservations: 2, + PhonedHome: 2, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + ds, mock = datastore.InitMockDB(t) + body = &v1.PartitionCapacityRequest{} + ws = NewPartition(slog.Default(), ds, nil) + ) + + if tt.mockFn != nil { + tt.mockFn(mock) + } + + code, got := genericWebRequest[[]*v1.PartitionCapacity](t, ws, testViewUser, body, "POST", "/v1/partition/capacity") + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } }) } - mock.On(r.DB("mockdb").Table("event")).Return(ecs, nil) - - testdata.InitMockDBData(mock) - log := slog.Default() - - service := NewPartition(log, ds, &nopTopicCreator{}) - container := restful.NewContainer().Add(service) - - pcRequest := &v1.PartitionCapacityRequest{} - js, err := json.Marshal(pcRequest) - require.NoError(t, err) - body := bytes.NewBuffer(js) - - req := httptest.NewRequest("POST", "/v1/partition/capacity", body) - req.Header.Add("Content-Type", "application/json") - container = injectAdmin(log, container, req) - w := httptest.NewRecorder() - container.ServeHTTP(w, req) - - resp := w.Result() - defer resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) - var result []v1.PartitionCapacity - err = json.NewDecoder(resp.Body).Decode(&result) - - require.NoError(t, err) - require.Len(t, result, 1) - require.Equal(t, testdata.Partition1.ID, result[0].ID) - require.NotNil(t, result[0].ServerCapacities) - require.Len(t, result[0].ServerCapacities, 1) - c := result[0].ServerCapacities[0] - require.Equal(t, "1", c.Size) - require.Equal(t, 5, c.Total) - require.Equal(t, 0, c.Free) - require.Equal(t, 3, c.Reservations) - require.Equal(t, 1, c.UsedReservations) } diff --git a/cmd/metal-api/internal/service/project-service.go b/cmd/metal-api/internal/service/project-service.go index db7731596..457237dce 100644 --- a/cmd/metal-api/internal/service/project-service.go +++ b/cmd/metal-api/internal/service/project-service.go @@ -245,19 +245,15 @@ func (r *projectResource) deleteProject(request *restful.Request, response *rest return } - sizes, err := r.ds.ListSizes() + var sizeReservations metal.SizeReservations + err = r.ds.SearchSizeReservations(&datastore.SizeReservationSearchQuery{ + Project: &id, + }, &sizeReservations) if err != nil { r.sendError(request, response, defaultError(err)) return } - var sizeReservations metal.Reservations - for _, size := range sizes { - size := size - - sizeReservations = size.Reservations.ForProject(id) - } - if len(sizeReservations) > 0 { r.sendError(request, response, httperrors.BadRequest(errors.New("there are still size reservations made by this project"))) return diff --git a/cmd/metal-api/internal/service/project-service_test.go b/cmd/metal-api/internal/service/project-service_test.go index 10f9b51f3..ccc0c1498 100644 --- a/cmd/metal-api/internal/service/project-service_test.go +++ b/cmd/metal-api/internal/service/project-service_test.go @@ -245,6 +245,7 @@ func Test_projectResource_deleteProject(t *testing.T) { mock.On(r.DB("mockdb").Table("network").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.Networks{}, nil) mock.On(r.DB("mockdb").Table("ip").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.IPs{}, nil) mock.On(r.DB("mockdb").Table("size")).Return([]metal.Size{}, nil) + mock.On(r.DB("mockdb").Table("sizereservation").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.SizeReservation{}, nil) }, want: &v1.ProjectResponse{}, wantStatus: 200, diff --git a/cmd/metal-api/internal/service/size-service.go b/cmd/metal-api/internal/service/size-service.go index 2be4d54a3..c5b313363 100644 --- a/cmd/metal-api/internal/service/size-service.go +++ b/cmd/metal-api/internal/service/size-service.go @@ -1,6 +1,7 @@ package service import ( + "errors" "fmt" "log/slog" "net/http" @@ -12,7 +13,7 @@ import ( "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-lib/auditing" - "google.golang.org/protobuf/types/known/wrapperspb" + "github.com/metal-stack/metal-lib/pkg/pointer" restfulspec "github.com/emicklei/go-restful-openapi/v2" restful "github.com/emicklei/go-restful/v3" @@ -64,27 +65,6 @@ func (r *sizeResource) webService() *restful.WebService { Returns(http.StatusOK, "OK", []v1.SizeResponse{}). DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - ws.Route(ws.POST("/reservations"). - To(r.listSizeReservations). - Operation("listSizeReservations"). - Doc("get all size reservations"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Metadata(auditing.Exclude, true). - Reads(v1.SizeReservationListRequest{}). - Writes([]v1.SizeReservationResponse{}). - Returns(http.StatusOK, "OK", []v1.SizeReservationResponse{}). - DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - - ws.Route(ws.POST("/suggest"). - To(r.suggestSize). - Operation("suggest"). - Doc("from a given machine id returns the appropriate size"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Metadata(auditing.Exclude, true). - Reads(v1.SizeSuggestRequest{}). - Returns(http.StatusOK, "OK", []v1.SizeConstraint{}). - DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - ws.Route(ws.DELETE("/{id}"). To(admin(r.deleteSize)). Operation("deleteSize"). @@ -115,6 +95,92 @@ func (r *sizeResource) webService() *restful.WebService { Returns(http.StatusConflict, "Conflict", httperrors.HTTPErrorResponse{}). DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + // suggest + + ws.Route(ws.POST("/suggest"). + To(r.suggestSize). + Operation("suggest"). + Doc("from a given machine id returns the appropriate size"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Metadata(auditing.Exclude, true). + Reads(v1.SizeSuggestRequest{}). + Returns(http.StatusOK, "OK", []v1.SizeConstraint{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + // size reservations + + ws.Route(ws.GET("/reservations"). + To(r.listSizeReservations). + Operation("listSizeReservations"). + Doc("get all size reservations"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Metadata(auditing.Exclude, true). + Writes([]v1.SizeReservationResponse{}). + Returns(http.StatusOK, "OK", []v1.SizeReservationResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.POST("/reservations/find"). + To(r.findSizeReservations). + Operation("findSizeReservations"). + Doc("get all size reservations"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Metadata(auditing.Exclude, true). + Reads(v1.SizeReservationListRequest{}). + Writes([]v1.SizeReservationResponse{}). + Returns(http.StatusOK, "OK", []v1.SizeReservationResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.GET("/reservations/{id}"). + To(r.getSizeReservation). + Operation("getSizeReservation"). + Doc("get size reservation by id"). + Param(ws.PathParameter("id", "identifier of the size reservation").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(v1.SizeReservationResponse{}). + Returns(http.StatusOK, "OK", v1.SizeReservationResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.DELETE("/reservations/{id}"). + To(editor(r.deleteSizeReservation)). + Operation("deleteSizeReservation"). + Doc("deletes a size reservation and returns the deleted entity"). + Param(ws.PathParameter("id", "identifier of the size reservation").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(v1.SizeReservationResponse{}). + Returns(http.StatusOK, "OK", v1.SizeReservationResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.PUT("/reservations"). + To(editor(r.createSizeReservation)). + Operation("createSizeReservation"). + Doc("create a size reservation. if the given ID already exists a conflict is returned"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(v1.SizeReservationCreateRequest{}). + Returns(http.StatusCreated, "Created", v1.SizeReservationResponse{}). + Returns(http.StatusConflict, "Conflict", httperrors.HTTPErrorResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.POST("/reservations"). + To(editor(r.updateSizeReservation)). + Operation("updateSizeReservation"). + Doc("updates a size reservation. if the size reservation was changed since this one was read, a conflict is returned"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(v1.SizeReservationUpdateRequest{}). + Returns(http.StatusOK, "OK", v1.SizeReservationResponse{}). + Returns(http.StatusConflict, "Conflict", httperrors.HTTPErrorResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + + ws.Route(ws.POST("/reservations/usage"). + To(r.sizeReservationsUsage). + Operation("sizeReservationsUsage"). + Doc("get all size reservations"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Metadata(auditing.Exclude, true). + Reads(v1.SizeReservationListRequest{}). + Writes([]v1.SizeReservationUsageResponse{}). + Returns(http.StatusOK, "OK", []v1.SizeReservationUsageResponse{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + return ws } @@ -269,16 +335,6 @@ func (r *sizeResource) createSize(request *restful.Request, response *restful.Re } constraints = append(constraints, constraint) } - var reservations metal.Reservations - for _, r := range requestPayload.SizeReservations { - reservations = append(reservations, metal.Reservation{ - Amount: r.Amount, - Description: r.Description, - ProjectID: r.ProjectID, - PartitionIDs: r.PartitionIDs, - Labels: r.Labels, - }) - } s := &metal.Size{ Base: metal.Base{ @@ -286,9 +342,8 @@ func (r *sizeResource) createSize(request *restful.Request, response *restful.Re Name: name, Description: description, }, - Constraints: constraints, - Reservations: reservations, - Labels: labels, + Constraints: constraints, + Labels: labels, } ss, err := r.ds.ListSizes() @@ -338,6 +393,20 @@ func (r *sizeResource) deleteSize(request *restful.Request, response *restful.Re return } + var rvs metal.SizeReservations + err = r.ds.SearchSizeReservations(&datastore.SizeReservationSearchQuery{ + SizeID: &s.ID, + }, &rvs) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + if len(rvs) > 0 { + r.sendError(request, response, httperrors.UnprocessableEntity(errors.New("cannot delete size before all size reservations were removed"))) + return + } + err = r.ds.DeleteSize(s) if err != nil { r.sendError(request, response, defaultError(err)) @@ -386,19 +455,6 @@ func (r *sizeResource) updateSize(request *restful.Request, response *restful.Re } newSize.Constraints = constraints } - var reservations metal.Reservations - if requestPayload.SizeReservations != nil { - for _, r := range requestPayload.SizeReservations { - reservations = append(reservations, metal.Reservation{ - Amount: r.Amount, - Description: r.Description, - ProjectID: r.ProjectID, - PartitionIDs: r.PartitionIDs, - Labels: r.Labels, - }) - } - newSize.Reservations = reservations - } ss, err := r.ds.ListSizes() if err != nil { @@ -439,6 +495,21 @@ func (r *sizeResource) updateSize(request *restful.Request, response *restful.Re } func (r *sizeResource) listSizeReservations(request *restful.Request, response *restful.Response) { + rvs, err := r.ds.ListSizeReservations() + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + result := []*v1.SizeReservationResponse{} + for i := range rvs { + result = append(result, v1.NewSizeReservationResponse(&rvs[i])) + } + + r.send(request, response, http.StatusOK, result) +} + +func (r *sizeResource) findSizeReservations(request *restful.Request, response *restful.Response) { var requestPayload v1.SizeReservationListRequest err := request.ReadEntity(&requestPayload) if err != nil { @@ -446,29 +517,194 @@ func (r *sizeResource) listSizeReservations(request *restful.Request, response * return } - ss := metal.Sizes{} - err = r.ds.SearchSizes(&datastore.SizeSearchQuery{ - ID: requestPayload.SizeID, - Reservation: datastore.Reservation{ - Partition: requestPayload.PartitionID, - Project: requestPayload.ProjectID, + var rvs metal.SizeReservations + err = r.ds.SearchSizeReservations(&datastore.SizeReservationSearchQuery{ + SizeID: requestPayload.SizeID, + Partition: requestPayload.PartitionID, + Project: requestPayload.ProjectID, + ID: requestPayload.ID, + }, &rvs) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + result := []*v1.SizeReservationResponse{} + for i := range rvs { + result = append(result, v1.NewSizeReservationResponse(&rvs[i])) + } + + r.send(request, response, http.StatusOK, result) +} + +func (r *sizeResource) getSizeReservation(request *restful.Request, response *restful.Response) { + id := request.PathParameter("id") + + rv, err := r.ds.FindSizeReservation(id) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + r.send(request, response, http.StatusOK, v1.NewSizeReservationResponse(rv)) +} + +func (r *sizeResource) createSizeReservation(request *restful.Request, response *restful.Response) { + var requestPayload v1.SizeReservationCreateRequest + err := request.ReadEntity(&requestPayload) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + + rv := &metal.SizeReservation{ + Base: metal.Base{ + ID: requestPayload.ID, + Name: pointer.SafeDeref(requestPayload.Name), + Description: pointer.SafeDeref(requestPayload.Description), }, - }, &ss) + SizeID: requestPayload.SizeID, + Amount: requestPayload.Amount, + ProjectID: requestPayload.ProjectID, + PartitionIDs: requestPayload.PartitionIDs, + Labels: requestPayload.Labels, + } + + size, err := r.ds.FindSize(requestPayload.SizeID) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + ps, err := r.ds.ListPartitions() + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + project, err := r.mdc.Project().Get(request.Request.Context(), &mdmv1.ProjectGetRequest{Id: requestPayload.ProjectID}) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + err = rv.Validate(metal.SizeMap{requestPayload.SizeID: *size}, ps.ByID(), map[string]*mdmv1.Project{requestPayload.ProjectID: project.Project}) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + + err = r.ds.CreateSizeReservation(rv) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + r.send(request, response, http.StatusCreated, v1.NewSizeReservationResponse(rv)) +} + +func (r *sizeResource) updateSizeReservation(request *restful.Request, response *restful.Response) { + var requestPayload v1.SizeReservationUpdateRequest + err := request.ReadEntity(&requestPayload) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + + oldRv, err := r.ds.FindSizeReservation(requestPayload.ID) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + rv := *oldRv + + if requestPayload.Name != nil { + rv.Name = *requestPayload.Name + } + if requestPayload.Description != nil { + rv.Description = *requestPayload.Description + } + if requestPayload.Labels != nil { + if len(requestPayload.Labels) == 0 { + rv.Labels = nil + } else { + rv.Labels = requestPayload.Labels + } + } + if requestPayload.Amount != nil { + rv.Amount = *requestPayload.Amount + } + if len(requestPayload.PartitionIDs) > 0 { + rv.PartitionIDs = requestPayload.PartitionIDs + } + + size, err := r.ds.FindSize(rv.SizeID) if err != nil { r.sendError(request, response, defaultError(err)) return } - pfr := &mdmv1.ProjectFindRequest{} + ps, err := r.ds.ListPartitions() + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } - if requestPayload.ProjectID != nil { - pfr.Id = wrapperspb.String(*requestPayload.ProjectID) + project, err := r.mdc.Project().Get(request.Request.Context(), &mdmv1.ProjectGetRequest{Id: rv.ProjectID}) + if err != nil { + r.sendError(request, response, defaultError(err)) + return } - if requestPayload.Tenant != nil { - pfr.TenantId = wrapperspb.String(*requestPayload.Tenant) + + err = rv.Validate(metal.SizeMap{rv.SizeID: *size}, ps.ByID(), map[string]*mdmv1.Project{rv.ProjectID: project.Project}) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return } - projects, err := r.mdc.Project().Find(request.Request.Context(), pfr) + err = r.ds.UpdateSizeReservation(oldRv, &rv) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + r.send(request, response, http.StatusOK, v1.NewSizeReservationResponse(&rv)) +} + +func (r *sizeResource) deleteSizeReservation(request *restful.Request, response *restful.Response) { + id := request.PathParameter("id") + + rv, err := r.ds.FindSizeReservation(id) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + err = r.ds.DeleteSizeReservation(rv) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + r.send(request, response, http.StatusOK, v1.NewSizeReservationResponse(rv)) +} + +func (r *sizeResource) sizeReservationsUsage(request *restful.Request, response *restful.Response) { + var requestPayload v1.SizeReservationListRequest + err := request.ReadEntity(&requestPayload) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + + rvs := metal.SizeReservations{} + err = r.ds.SearchSizeReservations(&datastore.SizeReservationSearchQuery{ + ID: requestPayload.ID, + SizeID: requestPayload.SizeID, + Partition: requestPayload.PartitionID, + Project: requestPayload.ProjectID, + }, &rvs) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -484,37 +720,27 @@ func (r *sizeResource) listSizeReservations(request *restful.Request, response * } var ( - result []*v1.SizeReservationResponse - projectsByID = projectsByID(projects.Projects) + result []*v1.SizeReservationUsageResponse machinesByProjectID = ms.ByProjectID() ) - for _, size := range ss { - size := size - - for _, reservation := range size.Reservations { - reservation := reservation - - project, ok := projectsByID[reservation.ProjectID] - if !ok { - continue - } - - for _, partitionID := range reservation.PartitionIDs { - allocations := len(machinesByProjectID[reservation.ProjectID].WithPartition(partitionID).WithSize(size.ID)) - - result = append(result, &v1.SizeReservationResponse{ - SizeID: size.ID, - PartitionID: partitionID, - Tenant: project.TenantId, - ProjectID: reservation.ProjectID, - ProjectName: project.Name, - Reservations: reservation.Amount, - UsedReservations: min(reservation.Amount, allocations), - ProjectAllocations: allocations, - Labels: reservation.Labels, - }) - } + for _, reservation := range rvs { + for _, partitionID := range reservation.PartitionIDs { + allocations := len(machinesByProjectID[reservation.ProjectID].WithPartition(partitionID).WithSize(reservation.SizeID)) + + result = append(result, &v1.SizeReservationUsageResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: reservation.ID}, + Describable: v1.Describable{Name: &reservation.Name, Description: &reservation.Description}, + }, + SizeID: reservation.SizeID, + PartitionID: partitionID, + ProjectID: reservation.ProjectID, + Amount: reservation.Amount, + UsedAmount: min(reservation.Amount, allocations), + ProjectAllocations: allocations, + Labels: reservation.Labels, + }) } } diff --git a/cmd/metal-api/internal/service/size-service_test.go b/cmd/metal-api/internal/service/size-service_test.go index e51367803..87c9a0415 100644 --- a/cmd/metal-api/internal/service/size-service_test.go +++ b/cmd/metal-api/internal/service/size-service_test.go @@ -10,6 +10,7 @@ import ( restful "github.com/emicklei/go-restful/v3" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdmv1mock "github.com/metal-stack/masterdata-api/api/v1/mocks" mdm "github.com/metal-stack/masterdata-api/pkg/client" @@ -22,7 +23,6 @@ import ( "github.com/stretchr/testify/assert" testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/wrapperspb" r "gopkg.in/rethinkdb/rethinkdb-go.v6" ) @@ -253,15 +253,6 @@ func TestCreateSize(t *testing.T) { Max: 100, }, }, - SizeReservations: []v1.SizeReservation{ - { - Amount: 3, - ProjectID: "a", - PartitionIDs: []string{testdata.Partition1.ID}, - Description: "test", - Labels: map[string]string{"a": "b"}, - }, - }, } js, err := json.Marshal(createRequest) require.NoError(t, err) @@ -342,35 +333,31 @@ func TestUpdateSize(t *testing.T) { require.Equal(t, maxCores, result.SizeConstraints[0].Max) } -func TestListSizeReservations(t *testing.T) { +func Test_ListSizeReservationsUsage(t *testing.T) { tests := []struct { name string req *v1.SizeReservationListRequest dbMockFn func(mock *r.Mock) projectMockFn func(mock *testifymock.Mock) - want []*v1.SizeReservationResponse + want []*v1.SizeReservationUsageResponse }{ { - name: "list reservations", + name: "list reservations usage", req: &v1.SizeReservationListRequest{ SizeID: pointer.Pointer("1"), - Tenant: pointer.Pointer("t1"), ProjectID: pointer.Pointer("p1"), PartitionID: pointer.Pointer("a"), }, dbMockFn: func(mock *r.Mock) { - mock.On(r.DB("mockdb").Table("size").Filter(r.MockAnything()).Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.Sizes{ + mock.On(r.DB("mockdb").Table("sizereservation").Filter(r.MockAnything()).Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.SizeReservations{ { Base: metal.Base{ ID: "1", }, - Reservations: metal.Reservations{ - { - Amount: 3, - PartitionIDs: []string{"a"}, - ProjectID: "p1", - }, - }, + SizeID: "1", + Amount: 3, + PartitionIDs: []string{"a"}, + ProjectID: "p1", }, }, nil) mock.On(r.DB("mockdb").Table("machine").Filter(r.MockAnything())).Return(metal.Machines{ @@ -386,22 +373,22 @@ func TestListSizeReservations(t *testing.T) { }, }, nil) }, - projectMockFn: func(mock *testifymock.Mock) { - mock.On("Find", testifymock.Anything, &mdmv1.ProjectFindRequest{ - Id: wrapperspb.String("p1"), - TenantId: wrapperspb.String("t1"), - }).Return(&mdmv1.ProjectListResponse{Projects: []*mdmv1.Project{ - {Meta: &mdmv1.Meta{Id: "p1"}, TenantId: "t1"}, - }}, nil) - }, - want: []*v1.SizeReservationResponse{ + want: []*v1.SizeReservationUsageResponse{ { + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, SizeID: "1", PartitionID: "a", - Tenant: "t1", ProjectID: "p1", - Reservations: 3, - UsedReservations: 1, + Amount: 3, + UsedAmount: 1, ProjectAllocations: 1, }, }, @@ -423,7 +410,69 @@ func TestListSizeReservations(t *testing.T) { tt.projectMockFn(&projectMock.Mock) } - code, got := genericWebRequest[[]*v1.SizeReservationResponse](t, ws, testViewUser, tt.req, "POST", "/v1/size/reservations") + code, got := genericWebRequest[[]*v1.SizeReservationUsageResponse](t, ws, testViewUser, tt.req, "POST", "/v1/size/reservations/usage") + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } +} + +func Test_ListSizeReservations(t *testing.T) { + tests := []struct { + name string + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want []*v1.SizeReservationResponse + }{ + { + name: "list reservations", + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("sizereservation")).Return(metal.SizeReservations{ + { + Base: metal.Base{ID: "1"}, + SizeID: "1", + Amount: 3, + PartitionIDs: []string{"a"}, + ProjectID: "p1", + }, + }, nil) + }, + projectMockFn: func(mock *testifymock.Mock) { + }, + want: []*v1.SizeReservationResponse{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "1"}, + Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[[]*v1.SizeReservationResponse](t, ws, testViewUser, nil, "GET", "/v1/size/reservations") assert.Equal(t, http.StatusOK, code) if diff := cmp.Diff(tt.want, got); diff != "" { @@ -431,7 +480,366 @@ func TestListSizeReservations(t *testing.T) { } }) } +} +func Test_FindSizeReservationsUsage(t *testing.T) { + tests := []struct { + name string + req *v1.SizeReservationListRequest + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want []*v1.SizeReservationResponse + }{ + { + name: "find reservations", + req: &v1.SizeReservationListRequest{ + SizeID: pointer.Pointer("1"), + ProjectID: pointer.Pointer("p1"), + PartitionID: pointer.Pointer("a"), + }, + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("sizereservation").Filter(r.MockAnything()).Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.SizeReservations{ + { + Base: metal.Base{ + ID: "1", + }, + SizeID: "1", + Amount: 3, + PartitionIDs: []string{"a"}, + ProjectID: "p1", + }, + }, nil) + }, + want: []*v1.SizeReservationResponse{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[[]*v1.SizeReservationResponse](t, ws, testViewUser, tt.req, "POST", "/v1/size/reservations/find") + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } +} + +func Test_GetSizeReservationsUsage(t *testing.T) { + tests := []struct { + name string + id string + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want *v1.SizeReservationResponse + }{ + { + name: "get reservation", + id: "1", + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("sizereservation").Get("1")).Return(metal.SizeReservation{ + Base: metal.Base{ + ID: "1", + }, + SizeID: "1", + Amount: 3, + PartitionIDs: []string{"a"}, + ProjectID: "p1", + }, nil) + }, + want: &v1.SizeReservationResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[*v1.SizeReservationResponse](t, ws, testViewUser, nil, "GET", "/v1/size/reservations/"+tt.id) + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } +} + +func Test_DeleteSizeReservationsUsage(t *testing.T) { + tests := []struct { + name string + id string + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want *v1.SizeReservationResponse + }{ + { + name: "delete reservation", + id: "1", + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("sizereservation").Get("1")).Return(metal.SizeReservation{ + Base: metal.Base{ + ID: "1", + }, + SizeID: "1", + Amount: 3, + PartitionIDs: []string{"a"}, + ProjectID: "p1", + }, nil) + + mock.On(r.DB("mockdb").Table("sizereservation").Get("1").Delete()).Return(testdata.EmptyResult, nil) + }, + want: &v1.SizeReservationResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer(""), + }, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[*v1.SizeReservationResponse](t, ws, testAdminUser, nil, "DELETE", "/v1/size/reservations/"+tt.id) + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } +} + +func Test_CreateSizeReservationsUsage(t *testing.T) { + tests := []struct { + name string + req *v1.SizeReservationCreateRequest + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want *v1.SizeReservationResponse + }{ + { + name: "create reservation", + req: &v1.SizeReservationCreateRequest{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Description: pointer.Pointer("a description"), + }, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + Labels: map[string]string{ + "a": "b", + }, + }, + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("size").Get("1")).Return(metal.Size{Base: metal.Base{ID: "1"}}, nil) + mock.On(r.DB("mockdb").Table("partition")).Return(metal.Partitions{{Base: metal.Base{ID: "a"}}}, nil) + mock.On(r.DB("mockdb").Table("sizereservation").Insert(r.MockAnything())).Return(testdata.EmptyResult, nil) + }, + projectMockFn: func(mock *testifymock.Mock) { + mock.On("Get", testifymock.Anything, &mdmv1.ProjectGetRequest{Id: "p1"}).Return(&mdmv1.ProjectResponse{Project: &mdmv1.Project{Meta: &mdmv1.Meta{Id: "p1"}}}, nil) + }, + want: &v1.SizeReservationResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer("a description"), + }, + }, + SizeID: "1", + PartitionIDs: []string{"a"}, + ProjectID: "p1", + Amount: 3, + Labels: map[string]string{ + "a": "b", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[*v1.SizeReservationResponse](t, ws, testAdminUser, tt.req, "PUT", "/v1/size/reservations") + assert.Equal(t, http.StatusCreated, code) + + if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(v1.SizeReservationResponse{}, "Timestamps")); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } +} + +func Test_UpdateSizeReservationsUsage(t *testing.T) { + tests := []struct { + name string + req *v1.SizeReservationUpdateRequest + dbMockFn func(mock *r.Mock) + projectMockFn func(mock *testifymock.Mock) + want *v1.SizeReservationResponse + }{ + { + name: "update reservation", + req: &v1.SizeReservationUpdateRequest{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "1"}, + Describable: v1.Describable{Description: pointer.Pointer("b description")}, + }, + PartitionIDs: []string{"b"}, + Amount: pointer.Pointer(4), + Labels: map[string]string{"c": "d"}, + }, + dbMockFn: func(mock *r.Mock) { + mock.On(r.DB("mockdb").Table("size").Get("1")).Return(metal.Size{Base: metal.Base{ID: "1"}}, nil) + mock.On(r.DB("mockdb").Table("sizereservation").Get("1")).Return(metal.SizeReservation{Base: metal.Base{ID: "1"}, SizeID: "1", ProjectID: "p1"}, nil) + mock.On(r.DB("mockdb").Table("partition")).Return(metal.Partitions{{Base: metal.Base{ID: "b"}}}, nil) + mock.On(r.DB("mockdb").Table("sizereservation").Get("1").Replace(r.MockAnything())).Return(testdata.EmptyResult, nil) + }, + projectMockFn: func(mock *testifymock.Mock) { + mock.On("Get", testifymock.Anything, &mdmv1.ProjectGetRequest{Id: "p1"}).Return(&mdmv1.ProjectResponse{Project: &mdmv1.Project{Meta: &mdmv1.Meta{Id: "p1"}}}, nil) + }, + want: &v1.SizeReservationResponse{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: "1", + }, + Describable: v1.Describable{ + Name: pointer.Pointer(""), + Description: pointer.Pointer("b description"), + }, + }, + SizeID: "1", + PartitionIDs: []string{"b"}, + ProjectID: "p1", + Amount: 4, + Labels: map[string]string{ + "c": "d", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + projectMock = mdmv1mock.NewProjectServiceClient(t) + m = mdm.NewMock(projectMock, nil, nil, nil) + ds, dbMock = datastore.InitMockDB(t) + ws = NewSize(slog.Default(), ds, m) + ) + + if tt.dbMockFn != nil { + tt.dbMockFn(dbMock) + } + if tt.projectMockFn != nil { + tt.projectMockFn(&projectMock.Mock) + } + + code, got := genericWebRequest[*v1.SizeReservationResponse](t, ws, testAdminUser, tt.req, "POST", "/v1/size/reservations") + assert.Equal(t, http.StatusOK, code) + + if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(v1.SizeReservationResponse{}, "Timestamps")); diff != "" { + t.Errorf("diff (-want +got):\n%s", diff) + } + }) + } } func Test_longestCommonPrefix(t *testing.T) { diff --git a/cmd/metal-api/internal/service/v1/machine.go b/cmd/metal-api/internal/service/v1/machine.go index 3e46cbb7b..07de84cda 100644 --- a/cmd/metal-api/internal/service/v1/machine.go +++ b/cmd/metal-api/internal/service/v1/machine.go @@ -162,16 +162,17 @@ type MachineBIOS struct { } type MachineIPMI struct { - Address string `json:"address" modelDescription:"The IPMI connection data"` - MacAddress string `json:"mac"` - User string `json:"user"` - Password string `json:"password"` - Interface string `json:"interface"` - Fru MachineFru `json:"fru"` - BMCVersion string `json:"bmcversion"` - PowerState string `json:"powerstate"` - PowerMetric *PowerMetric `json:"powermetric"` - LastUpdated time.Time `json:"last_updated"` + Address string `json:"address" modelDescription:"The IPMI connection data"` + MacAddress string `json:"mac"` + User string `json:"user"` + Password string `json:"password"` + Interface string `json:"interface"` + Fru MachineFru `json:"fru"` + BMCVersion string `json:"bmcversion"` + PowerState string `json:"powerstate"` + PowerMetric *PowerMetric `json:"powermetric"` + PowerSupplies PowerSupplies `json:"powersupplies"` + LastUpdated time.Time `json:"last_updated"` } type PowerMetric struct { @@ -193,6 +194,16 @@ type PowerMetric struct { // IntervalInMin minutes. MinConsumedWatts float32 `json:"minconsumedwatts"` } +type PowerSupplies []PowerSupply +type PowerSupply struct { + // Status shall contain any status or health properties + // of the resource. + Status PowerSupplyStatus `json:"status"` +} +type PowerSupplyStatus struct { + Health string `json:"health"` + State string `json:"state"` +} type MachineFru struct { ChassisPartNumber *string `json:"chassis_part_number,omitempty" modelDescription:"The Field Replaceable Unit data" description:"the chassis part number" optional:"true"` @@ -274,6 +285,7 @@ type MachineIpmiReport struct { PowerState string IndicatorLEDState string PowerMetric *PowerMetric + PowerSupplies PowerSupplies } type MachineIpmiReports struct { @@ -366,6 +378,15 @@ func NewMetalIPMI(r *MachineIPMI) metal.IPMI { MinConsumedWatts: r.PowerMetric.MinConsumedWatts, } } + var powerSupplies metal.PowerSupplies + for _, ps := range r.PowerSupplies { + powerSupplies = append(powerSupplies, metal.PowerSupply{ + Status: metal.PowerSupplyStatus{ + Health: ps.Status.Health, + State: ps.Status.State, + }, + }) + } return metal.IPMI{ Address: r.Address, @@ -385,8 +406,9 @@ func NewMetalIPMI(r *MachineIPMI) metal.IPMI { ProductPartNumber: productPartNumber, ProductSerial: productSerial, }, - PowerState: r.PowerState, - PowerMetric: powerMetric, + PowerState: r.PowerState, + PowerMetric: powerMetric, + PowerSupplies: powerSupplies, } } @@ -402,20 +424,30 @@ func NewMachineIPMIResponse(m *metal.Machine, s *metal.Size, p *metal.Partition, MinConsumedWatts: m.IPMI.PowerMetric.MinConsumedWatts, } } + var powerSupplies PowerSupplies + for _, ps := range m.IPMI.PowerSupplies { + powerSupplies = append(powerSupplies, PowerSupply{ + Status: PowerSupplyStatus{ + Health: ps.Status.Health, + State: ps.Status.State, + }, + }) + } return &MachineIPMIResponse{ Common: machineResponse.Common, MachineBase: machineResponse.MachineBase, IPMI: MachineIPMI{ - Address: m.IPMI.Address, - MacAddress: m.IPMI.MacAddress, - User: m.IPMI.User, - Password: m.IPMI.Password, - Interface: m.IPMI.Interface, - BMCVersion: m.IPMI.BMCVersion, - PowerState: m.IPMI.PowerState, - PowerMetric: powerMetric, - LastUpdated: m.IPMI.LastUpdated, + Address: m.IPMI.Address, + MacAddress: m.IPMI.MacAddress, + User: m.IPMI.User, + Password: m.IPMI.Password, + Interface: m.IPMI.Interface, + BMCVersion: m.IPMI.BMCVersion, + PowerState: m.IPMI.PowerState, + PowerMetric: powerMetric, + PowerSupplies: powerSupplies, + LastUpdated: m.IPMI.LastUpdated, Fru: MachineFru{ ChassisPartNumber: &m.IPMI.Fru.ChassisPartNumber, ChassisPartSerial: &m.IPMI.Fru.ChassisPartSerial, diff --git a/cmd/metal-api/internal/service/v1/partition.go b/cmd/metal-api/internal/service/v1/partition.go index beed5d2fc..f3304241f 100644 --- a/cmd/metal-api/internal/service/v1/partition.go +++ b/cmd/metal-api/internal/service/v1/partition.go @@ -49,17 +49,47 @@ type PartitionCapacity struct { ServerCapacities ServerCapacities `json:"servers" description:"servers available in this partition"` } +// ServerCapacity holds the machine capacity of a partition of a specific size. +// The amount of allocated, waiting and other machines sum up to the total amount of machines. type ServerCapacity struct { - Size string `json:"size" description:"the size of the server"` - Total int `json:"total" description:"total amount of servers with this size"` - Free int `json:"free" description:"free servers with this size"` - Allocated int `json:"allocated" description:"allocated servers with this size"` - Reservations int `json:"reservations" description:"the amount of reservations for this size"` - UsedReservations int `json:"usedreservations" description:"the amount of used reservations for this size"` - Faulty int `json:"faulty" description:"servers with issues with this size"` - FaultyMachines []string `json:"faultymachines" description:"servers with issues with this size"` - Other int `json:"other" description:"servers neither free, allocated or faulty with this size"` - OtherMachines []string `json:"othermachines" description:"servers neither free, allocated or faulty with this size"` + // Size is the size id correlating to all counts in this server capacity. + Size string `json:"size" description:"the size of the machine"` + + // Total is the total amount of machines for this size. + Total int `json:"total,omitempty" description:"total amount of machines with size"` + + // PhonedHome is the amount of machines that are currently in the provisioning state "phoned home". + PhonedHome int `json:"phoned_home,omitempty" description:"machines in phoned home provisioning state"` + // Waiting is the amount of machines that are currently in the provisioning state "waiting". + Waiting int `json:"waiting,omitempty" description:"machines in waiting provisioning state"` + // Other is the amount of machines that are neither in the provisioning state waiting nor in phoned home but in another provisioning state. + Other int `json:"other,omitempty" description:"machines neither phoned home nor waiting but in another provisioning state"` + // OtherMachines contains the machine IDs for machines that were classified into "Other". + OtherMachines []string `json:"othermachines,omitempty" description:"machine ids neither allocated nor waiting with this size"` + + // Allocated is the amount of machines that are currently allocated. + Allocated int `json:"allocated,omitempty" description:"allocated machines"` + // Allocatable is the amount of machines in a partition is the amount of machines that can be allocated. + // Effectively this is the amount of waiting machines minus the machines that are unavailable due to machine state or un-allocatable. Size reservations are not considered in this count. + Allocatable int `json:"allocatable,omitempty" description:"free machines with this size, size reservations are not considered"` + // Free is the amount of machines in a partition that can be freely allocated at any given moment by a project. + // Effectively this is the amount of waiting machines minus the machines that are unavailable due to machine state or un-allocatable due to size reservations. + Free int `json:"free,omitempty" description:"free machines with this size (freely allocatable)"` + // Unavailable is the amount of machine in a partition that are currently not allocatable because they are not waiting or + // not in the machine state "available", e.g. locked or reserved. + Unavailable int `json:"unavailable,omitempty" description:"unavailable machines with this size"` + + // Faulty is the amount of machines that are neither allocated nor in the pool of available machines because they report an error. + Faulty int `json:"faulty,omitempty" description:"machines with issues with this size"` + // FaultyMachines contains the machine IDs for machines that were classified into "Faulty". + FaultyMachines []string `json:"faultymachines,omitempty" description:"machine ids with issues with this size"` + + // Reservations is the amount of reservations made for this size. + Reservations int `json:"reservations,omitempty" description:"the amount of reservations for this size"` + // UsedReservations is the amount of reservations already used up for this size. + UsedReservations int `json:"usedreservations,omitempty" description:"the amount of used reservations for this size"` + // RemainingReservations is the amount of reservations remaining for this size. + RemainingReservations int `json:"remainingreservations,omitempty" description:"the amount of unused / remaining / open reservations for this size"` } func NewPartitionResponse(p *metal.Partition) *PartitionResponse { diff --git a/cmd/metal-api/internal/service/v1/size.go b/cmd/metal-api/internal/service/v1/size.go index d37eed0c9..0b828724e 100644 --- a/cmd/metal-api/internal/service/v1/size.go +++ b/cmd/metal-api/internal/service/v1/size.go @@ -11,51 +11,65 @@ type SizeConstraint struct { Identifier string `json:"identifier,omitempty" description:"glob pattern which matches to the given type, for example gpu pci id"` } -type SizeReservation struct { - Amount int `json:"amount" description:"the amount of reserved machine allocations for this size"` - Description string `json:"description,omitempty" description:"a description for this reservation"` - ProjectID string `json:"projectid" description:"the project for which this size reservation is considered"` - PartitionIDs []string `json:"partitionids" description:"the partitions in which this size reservation is considered, the amount is valid for every partition"` - Labels map[string]string `json:"labels,omitempty" description:"free labels associated with this size reservation."` -} - type SizeCreateRequest struct { Common - SizeConstraints []SizeConstraint `json:"constraints" description:"a list of constraints that defines this size"` - SizeReservations []SizeReservation `json:"reservations,omitempty" description:"reservations for this size, which are considered during machine allocation" optional:"true"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this size." optional:"true"` + SizeConstraints []SizeConstraint `json:"constraints" description:"a list of constraints that defines this size"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this size." optional:"true"` } type SizeUpdateRequest struct { Common - SizeConstraints *[]SizeConstraint `json:"constraints" description:"a list of constraints that defines this size" optional:"true"` - SizeReservations []SizeReservation `json:"reservations,omitempty" description:"reservations for this size, which are considered during machine allocation" optional:"true"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this size." optional:"true"` + SizeConstraints *[]SizeConstraint `json:"constraints" description:"a list of constraints that defines this size" optional:"true"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this size." optional:"true"` } type SizeResponse struct { Common - SizeConstraints []SizeConstraint `json:"constraints" description:"a list of constraints that defines this size"` - SizeReservations []SizeReservation `json:"reservations,omitempty" description:"reservations for this size, which are considered during machine allocation" optional:"true"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this size."` + SizeConstraints []SizeConstraint `json:"constraints" description:"a list of constraints that defines this size"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this size."` Timestamps } +type SizeReservationCreateRequest struct { + Common + SizeID string `json:"sizeid" description:"the size id of this size reservation"` + PartitionIDs []string `json:"partitionids" description:"the partition id of this size reservation"` + ProjectID string `json:"projectid" description:"the project id of this size reservation"` + Amount int `json:"amount" description:"the amount of reservations of this size reservation"` + Labels map[string]string `json:"labels,omitempty" description:"free labels associated with this size reservation."` +} + +type SizeReservationUpdateRequest struct { + Common + PartitionIDs []string `json:"partitionids" description:"the partition id of this size reservation"` + Amount *int `json:"amount" description:"the amount of reservations of this size reservation"` + Labels map[string]string `json:"labels,omitempty" description:"free labels associated with this size reservation."` +} + type SizeReservationResponse struct { + Common + Timestamps + SizeID string `json:"sizeid" description:"the size id of this size reservation"` + PartitionIDs []string `json:"partitionids" description:"the partition id of this size reservation"` + ProjectID string `json:"projectid" description:"the project id of this size reservation"` + Amount int `json:"amount" description:"the amount of reservations of this size reservation"` + Labels map[string]string `json:"labels,omitempty" description:"free labels associated with this size reservation."` +} + +type SizeReservationUsageResponse struct { + Common SizeID string `json:"sizeid" description:"the size id of this size reservation"` PartitionID string `json:"partitionid" description:"the partition id of this size reservation"` - Tenant string `json:"tenant" description:"the tenant of this size reservation"` ProjectID string `json:"projectid" description:"the project id of this size reservation"` - ProjectName string `json:"projectname" description:"the project name of this size reservation"` - Reservations int `json:"reservations" description:"the amount of reservations of this size reservation"` - UsedReservations int `json:"usedreservations" description:"the used amount of reservations of this size reservation"` + Amount int `json:"amount" description:"the amount of reservations of this size reservation"` + UsedAmount int `json:"usedamount" description:"the used amount of reservations of this size reservation"` ProjectAllocations int `json:"projectallocations" description:"the amount of allocations of this project referenced by this size reservation"` Labels map[string]string `json:"labels,omitempty" description:"free labels associated with this size reservation."` } type SizeReservationListRequest struct { + ID *string `json:"id,omitempty" description:"the id of this size reservation"` SizeID *string `json:"sizeid,omitempty" description:"the size id of this size reservation"` - Tenant *string `json:"tenant,omitempty" description:"the tenant of this size reservation"` ProjectID *string `json:"projectid,omitempty" description:"the project id of this size reservation"` PartitionID *string `json:"partitionid,omitempty" description:"the partition id of this size reservation"` } @@ -93,18 +107,6 @@ func NewSizeResponse(s *metal.Size) *SizeResponse { constraints = append(constraints, constraint) } - reservations := []SizeReservation{} - for _, r := range s.Reservations { - reservation := SizeReservation{ - Amount: r.Amount, - Description: r.Description, - ProjectID: r.ProjectID, - PartitionIDs: r.PartitionIDs, - Labels: r.Labels, - } - reservations = append(reservations, reservation) - } - labels := map[string]string{} if s.Labels != nil { labels = s.Labels @@ -120,8 +122,7 @@ func NewSizeResponse(s *metal.Size) *SizeResponse { Description: &s.Description, }, }, - SizeReservations: reservations, - SizeConstraints: constraints, + SizeConstraints: constraints, Timestamps: Timestamps{ Created: s.Created, Changed: s.Changed, @@ -129,3 +130,15 @@ func NewSizeResponse(s *metal.Size) *SizeResponse { Labels: labels, } } + +func NewSizeReservationResponse(rv *metal.SizeReservation) *SizeReservationResponse { + return &SizeReservationResponse{ + Common: Common{Identifiable: Identifiable{ID: rv.ID}, Describable: Describable{Name: &rv.Name, Description: &rv.Description}}, + Timestamps: Timestamps{Created: rv.Created, Changed: rv.Changed}, + SizeID: rv.SizeID, + PartitionIDs: rv.PartitionIDs, + ProjectID: rv.ProjectID, + Amount: rv.Amount, + Labels: rv.Labels, + } +} diff --git a/cmd/metal-api/internal/service/vpn-service.go b/cmd/metal-api/internal/service/vpn-service.go index a81661694..a1584f7c3 100644 --- a/cmd/metal-api/internal/service/vpn-service.go +++ b/cmd/metal-api/internal/service/vpn-service.go @@ -107,7 +107,7 @@ func (r *vpnResource) getVPNAuthKey(request *restful.Request, response *restful. } type headscaleMachineLister interface { - MachinesConnected(ctx context.Context) ([]*headscalev1.Machine, error) + NodesConnected(ctx context.Context) ([]*headscalev1.Node, error) } func EvaluateVPNConnected(log *slog.Logger, ds *datastore.RethinkStore, lister headscaleMachineLister) error { @@ -119,7 +119,7 @@ func EvaluateVPNConnected(log *slog.Logger, ds *datastore.RethinkStore, lister h ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() - headscaleMachines, err := lister.MachinesConnected(ctx) + headscaleNodes, err := lister.NodesConnected(ctx) if err != nil { return err } @@ -131,7 +131,7 @@ func EvaluateVPNConnected(log *slog.Logger, ds *datastore.RethinkStore, lister h continue } - index := slices.IndexFunc(headscaleMachines, func(hm *headscalev1.Machine) bool { + index := slices.IndexFunc(headscaleNodes, func(hm *headscalev1.Node) bool { if hm.Name != m.ID { return false } @@ -147,7 +147,7 @@ func EvaluateVPNConnected(log *slog.Logger, ds *datastore.RethinkStore, lister h continue } - connected := headscaleMachines[index].Online + connected := headscaleNodes[index].Online if m.Allocation.VPN.Connected == connected { log.Info("not updating vpn because already up-to-date", "machine", m.ID, "connected", connected) diff --git a/cmd/metal-api/internal/service/vpn-service_test.go b/cmd/metal-api/internal/service/vpn-service_test.go index 783fd3c9f..ef17161cb 100644 --- a/cmd/metal-api/internal/service/vpn-service_test.go +++ b/cmd/metal-api/internal/service/vpn-service_test.go @@ -18,7 +18,7 @@ func Test_EvaluateVPNConnected(t *testing.T) { tests := []struct { name string mockFn func(mock *r.Mock) - headscaleMachines []*headscalev1.Machine + headscaleMachines []*headscalev1.Node wantErr error }{ { @@ -60,7 +60,7 @@ func Test_EvaluateVPNConnected(t *testing.T) { // unfortunately, it's too hard to check the replace exactly for specific fields... mock.On(r.DB("mockdb").Table("machine").Get("toggle").Replace(r.MockAnything())).Return(testdata.EmptyResult, nil) }, - headscaleMachines: []*headscalev1.Machine{ + headscaleMachines: []*headscalev1.Node{ { Name: "toggle", User: &headscalev1.User{ @@ -104,9 +104,9 @@ func Test_EvaluateVPNConnected(t *testing.T) { } type headscaleTest struct { - ms []*headscalev1.Machine + ms []*headscalev1.Node } -func (h *headscaleTest) MachinesConnected(ctx context.Context) ([]*headscalev1.Machine, error) { +func (h *headscaleTest) NodesConnected(ctx context.Context) ([]*headscalev1.Node, error) { return h.ms, nil } diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index f59c71402..0f8a5b9a1 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -191,13 +191,6 @@ var ( Max: 1000000000000, }, }, - Reservations: metal.Reservations{ - { - Amount: 3, - PartitionIDs: []string{Partition1.ID}, - ProjectID: "p1", - }, - }, } Sz2 = metal.Size{ Base: metal.Base{ @@ -874,6 +867,9 @@ func InitMockDBData(mock *r.Mock) { }, nil) mock.On(r.DB("mockdb").Table("integerpool").Get(r.MockAnything()).Delete(r.DeleteOpts{ReturnChanges: true})).Return(r.WriteResponse{Changes: []r.ChangeResponse{r.ChangeResponse{OldValue: map[string]interface{}{"id": float64(12345)}}}}, nil) + // Find + mock.On(r.DB("mockdb").Table("sizereservation").Filter(r.MockAnything())).Return(metal.SizeReservations{}, nil) + // Default: Return Empty result mock.On(r.DB("mockdb").Table("size").Get(r.MockAnything())).Return(EmptyResult, nil) mock.On(r.DB("mockdb").Table("partition").Get(r.MockAnything())).Return(EmptyResult, nil) diff --git a/go.mod b/go.mod index 94d5b13b5..ea0d32e31 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/metal-stack/metal-api -go 1.22 +go 1.23.0 require ( - connectrpc.com/connect v1.16.2 - github.com/Masterminds/semver/v3 v3.2.1 + connectrpc.com/connect v1.17.0 + github.com/Masterminds/semver/v3 v3.3.0 github.com/avast/retry-go/v4 v4.6.0 github.com/aws/aws-sdk-go v1.55.5 github.com/dustin/go-humanize v1.0.1 @@ -15,75 +15,62 @@ require ( github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 - github.com/juanfont/headscale v0.22.3 + github.com/juanfont/headscale v0.23.0 github.com/klauspost/connect-compress/v2 v2.0.0 github.com/looplab/fsm v1.0.2 - github.com/metal-stack/go-ipam v1.14.5 + github.com/metal-stack/go-ipam v1.14.7 github.com/metal-stack/masterdata-api v0.11.4 - github.com/metal-stack/metal-lib v0.18.1 + github.com/metal-stack/metal-lib v0.18.3 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.20.0 + github.com/prometheus/client_golang v1.20.4 github.com/samber/lo v1.47.0 github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 + github.com/spf13/viper v1.20.0-alpha.6 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.32.0 + github.com/testcontainers/testcontainers-go v0.33.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.27.0 golang.org/x/sync v0.8.0 - google.golang.org/grpc v1.65.0 + google.golang.org/grpc v1.67.0 google.golang.org/protobuf v1.34.2 gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 ) -replace ( - // Newer versions do not export base entities which are used to composite other entities. - // This breaks metalctl and friends - github.com/emicklei/go-restful-openapi/v2 => github.com/emicklei/go-restful-openapi/v2 v2.9.1 - // netipx and x/exp must be replaced for tailscale < 1.48 - go4.org/netipx => go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 - golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - inet.af/netaddr => inet.af/netaddr v0.0.0-20210511181906-37180328850c // indirect - // tailscale 1.48 and headscale 0.22 are not compatible yet - tailscale.com => tailscale.com v1.44.0 -) +// Newer versions do not export base entities which are used to composite other entities. +// This breaks metalctl and friends +replace github.com/emicklei/go-restful-openapi/v2 => github.com/emicklei/go-restful-openapi/v2 v2.9.1 require ( - dario.cat/mergo v1.0.0 // indirect - filippo.io/edwards25519 v1.1.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.5 // indirect - github.com/akutz/memconn v0.1.0 // indirect - github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/containerd v1.7.20 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-oidc/v3 v3.11.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/deckarep/golang-set/v2 v2.6.0 // indirect 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.2+incompatible // indirect + github.com/docker/docker v27.3.1+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 github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/glebarez/sqlite v1.11.0 // indirect + github.com/go-gormigrate/gormigrate/v2 v2.1.2 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -93,19 +80,15 @@ require ( github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/goccy/go-json v0.10.3 // 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 github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -117,31 +100,26 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect - github.com/jsimonetti/rtnetlink v1.4.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.10 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.5 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.1.0 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mdlayher/netlink v1.7.2 // indirect - github.com/mdlayher/socket v0.5.1 // indirect github.com/meilisearch/meilisearch-go v0.27.2 // indirect - github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect 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/user v0.3.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 @@ -155,27 +133,25 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/philip-bui/grpc-zerolog v1.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.59.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/redis/go-redis/v9 v9.6.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -183,8 +159,7 @@ require ( github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.55.0 // indirect - github.com/x448/float16 v0.8.4 // indirect + github.com/valyala/fasthttp v1.56.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect @@ -193,33 +168,31 @@ require ( go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect - go.mongodb.org/mongo-driver v1.16.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 + go.mongodb.org/mongo-driver v1.17.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 + go.opentelemetry.io/proto/otlp v1.3.1 // indirect 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-20240808152545-0cdaa3abc0fa - golang.org/x/net v0.28.0 // indirect - golang.org/x/oauth2 v0.22.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-20240814211410-ddb44dafa142 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.5.7 // indirect - gorm.io/gorm v1.25.10 // indirect - modernc.org/libc v1.50.9 // indirect + gorm.io/driver/postgres v1.5.9 // indirect + gorm.io/gorm v1.25.11 // indirect + modernc.org/libc v1.60.1 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.29.10 // indirect - nhooyr.io/websocket v1.8.11 // indirect - tailscale.com v1.66.4 // indirect + modernc.org/sqlite v1.32.0 // indirect + tailscale.com v1.72.1 // indirect ) diff --git a/go.sum b/go.sum index 1fb0b970e..0bf5c7c35 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,19 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= -connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk= +connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= -filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= -github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= -github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= -github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= @@ -33,7 +23,6 @@ github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinR github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= @@ -50,15 +39,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= -github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= -github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -69,26 +51,24 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= +github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= -github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 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.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= -github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+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= @@ -100,10 +80,6 @@ github.com/emicklei/go-restful-openapi/v2 v2.9.1/go.mod h1:VKNgZyYviM1hnyrjD9RDz github.com/emicklei/go-restful/v3 v3.7.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -111,16 +87,16 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY= +github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= +github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -157,54 +133,39 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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.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= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25 h1:sEDPKUw6iPjczdu33njxFjO6tYa9bfc0z/QyB/zSsBw= +github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 h1:nHoRIX8iXob3Y2kdt9KsjyIb7iApSvb3vgsd93xb5Ow= github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= @@ -230,25 +191,20 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= -github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/juanfont/headscale v0.22.3 h1:BHpPO9cIB1vBVyp0faEAk2Pq2Zi04NXXgsf3Wt60sac= -github.com/juanfont/headscale v0.22.3/go.mod h1:NdBJ+givOOABlLd7GFT2WRjGP4e/ylLYy+U1WIkpTIQ= +github.com/juanfont/headscale v0.23.0 h1:32FqxkHYEXosSCwlkej7q6968r2qMM5HHHU5M80OQsg= +github.com/juanfont/headscale v0.23.0/go.mod h1:ldIk/0YeSrQYwAC0GA8dsnqMsrs9bUIRJR+wBJ/TUnY= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= +github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/connect-compress/v2 v2.0.0 h1:L7TVsLa6Oo9Hkkb6r3DwSrhBbcWlXjneqBj7fCRXviU= github.com/klauspost/connect-compress/v2 v2.0.0/go.mod h1:604CD9JSAjGqtVzCM4SRgM/9TFTkWBcp+2wlQfGyJ6c= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -262,20 +218,20 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= -github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.0 h1:0zs7Ya6+39qoit7gwAf+cYm1zzgS3fceIdo7RmQ5lkw= -github.com/lestrrat-go/jwx/v2 v2.1.0/go.mod h1:Xpw9QIaUGiIUD1Wx0NcY1sIHwFf8lDuZn/cmxtXYRys= +github.com/lestrrat-go/jwx/v2 v2.1.1 h1:Y2ltVl8J6izLYFs54BVcpXLv5msSW4o8eXwnzZLI32E= +github.com/lestrrat-go/jwx/v2 v2.1.1/go.mod h1:4LvZg7oxu6Q5VJwn7Mk/UwooNRnTHUpXBj2C4j3HNx0= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/looplab/fsm v1.0.2 h1:f0kdMzr4CRpXtaKKRUxwLYJ7PirTdwrtNumeLN+mDx8= github.com/looplab/fsm v1.0.2/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN3opGfRpoQgAVqr6/A5+qRTi67VUZY= +github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -291,24 +247,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/meilisearch/meilisearch-go v0.27.2 h1:3G21dJ5i208shnLPDsIEZ0L0Geg/5oeXABFV7nlK94k= github.com/meilisearch/meilisearch-go v0.27.2/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= -github.com/metal-stack/go-ipam v1.14.5 h1:KSnftPoySufz/SSbAmtCqo/HzmlYuyVMSfMi53snFEE= -github.com/metal-stack/go-ipam v1.14.5/go.mod h1:K/ax3O8oPYIClpEpSLmu0a2NfKM/9qNrNLa05cdYndY= +github.com/metal-stack/go-ipam v1.14.7 h1:DA+uP72rAqGechwDJ3EzjI/snaMc77lGqoQYsyE4DUk= +github.com/metal-stack/go-ipam v1.14.7/go.mod h1:YxQhPVl9cXFSs3/DpyYa4qVsYT+aDIlwfCFLt4znTqI= github.com/metal-stack/masterdata-api v0.11.4 h1:bgRk7PbD5BjYbmAReaV7gTKKKrW5x/ZzCwj98VSWoJk= github.com/metal-stack/masterdata-api v0.11.4/go.mod h1:fD0AtsoNNaOLqRMBeZzDFljiQW9RlrOnxeZ20Pqhxas= -github.com/metal-stack/metal-lib v0.18.1 h1:Kjmf/Z/6pWemR8O6ttbNPQ9PjeT3ON60sBNu51Lgi1M= -github.com/metal-stack/metal-lib v0.18.1/go.mod h1:GJjipRpHmpd2vjBtsaw9gGk5ZFan7NlShyjIsTdY1x4= +github.com/metal-stack/metal-lib v0.18.3 h1:bovFiJPB9SMvuGLqcXVWz6jFB8HrdzwnCX7TFlen4r0= +github.com/metal-stack/metal-lib v0.18.3/go.mod h1:Ctyi6zaXFr2NVrQZLFsDLnFCzupKnYErTtgRFKAsnbw= github.com/metal-stack/security v0.8.1 h1:4zmVUxZvDWShVvVIxM3XhIv7pTmPe9DvACRIHW6YTsk= github.com/metal-stack/security v0.8.1/go.mod h1:OO8ZilZO6fUV5QEmwc7HP/RAjqYrGQxXoYIddJ9TvqE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= github.com/metal-stack/v v1.0.3/go.mod h1:YTahEu7/ishwpYKnp/VaW/7nf8+PInogkfGwLcGPdXg= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -317,8 +267,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 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/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 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= @@ -358,11 +308,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA= -github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -370,31 +317,28 @@ 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.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_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= -github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= 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.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= @@ -406,29 +350,26 @@ github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.20.0-alpha.6 h1:f65Cr/+2qk4GfHC0xqT/isoupQppwN5+VLRztUGTDbY= +github.com/spf13/viper v1.20.0-alpha.6/go.mod h1:CGBZzv0c9fOUASm6rfus4wdeIjR/04NOLq1P4KRhX3k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -437,15 +378,14 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= -github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= @@ -453,11 +393,9 @@ github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8t github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= -github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= +github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U= +github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -477,65 +415,50 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5 go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= -go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= -go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k= +go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -545,25 +468,20 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -573,7 +491,6 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -581,61 +498,40 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= -golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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-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= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 h1:pAjq8XSSzXoP9ya73v/w+9QEAAJNluLrpmMq5qFJQNY= +google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:O6rP0uBq4k0mdi/b4ZEMAZjkhYWhS815kCvaMha4VN8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= @@ -650,8 +546,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 h1:tczPZjdz6soV2thcuq1IFOuNLrBUGonFyUXBbIWXWis= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2/go.mod h1:c7Wo0IjB7JL9B9Avv0UZKorYJCUhiergpj3u1WtGT1E= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -662,29 +556,24 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= -gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= -gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= -gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= +gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= -modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= -modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= +modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= -modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s= +modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= @@ -693,15 +582,11 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= -modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= -nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= -software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= -tailscale.com v1.44.0 h1:MPos9n30kJvdyfL52045gVFyNg93K+bwgDsr8gqKq2o= -tailscale.com v1.44.0/go.mod h1:+iYwTdeHyVJuNDu42Zafwihq1Uqfh+pW7pRaY1GD328= +tailscale.com v1.72.1 h1:hk82jek36ph2S3Tfsh57NVWKEm/pZ9nfUonvlowpfaA= +tailscale.com v1.72.1/go.mod h1:v7OHtg0KLAnhOVf81Z8WrjNefj238QbFhgkWJQoKxbs= diff --git a/spec/metal-api.json b/spec/metal-api.json index 78948a6b7..d1485386d 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -2700,6 +2700,12 @@ "powerstate": { "type": "string" }, + "powersupplies": { + "items": { + "$ref": "#/definitions/v1.PowerSupply" + }, + "type": "array" + }, "user": { "type": "string" } @@ -2714,6 +2720,7 @@ "password", "powermetric", "powerstate", + "powersupplies", "user" ] }, @@ -2832,6 +2839,12 @@ }, "PowerState": { "type": "string" + }, + "PowerSupplies": { + "items": { + "$ref": "#/definitions/v1.PowerSupply" + }, + "type": "array" } }, "required": [ @@ -2841,7 +2854,8 @@ "FRU", "IndicatorLEDState", "PowerMetric", - "PowerState" + "PowerState", + "PowerSupplies" ] }, "v1.MachineIpmiReportResponse": { @@ -4283,6 +4297,30 @@ "minconsumedwatts" ] }, + "v1.PowerSupply": { + "properties": { + "status": { + "$ref": "#/definitions/v1.PowerSupplyStatus" + } + }, + "required": [ + "status" + ] + }, + "v1.PowerSupplyStatus": { + "properties": { + "health": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "required": [ + "health", + "state" + ] + }, "v1.Project": { "properties": { "description": { @@ -4441,51 +4479,71 @@ }, "v1.ServerCapacity": { "properties": { + "allocatable": { + "description": "free machines with this size, size reservations are not considered", + "format": "int32", + "type": "integer" + }, "allocated": { - "description": "allocated servers with this size", + "description": "allocated machines", "format": "int32", "type": "integer" }, "faulty": { - "description": "servers with issues with this size", + "description": "machines with issues with this size", "format": "int32", "type": "integer" }, "faultymachines": { - "description": "servers with issues with this size", + "description": "machine ids with issues with this size", "items": { "type": "string" }, "type": "array" }, "free": { - "description": "free servers with this size", + "description": "free machines with this size (freely allocatable)", "format": "int32", "type": "integer" }, "other": { - "description": "servers neither free, allocated or faulty with this size", + "description": "machines neither phoned home nor waiting but in another provisioning state", "format": "int32", "type": "integer" }, "othermachines": { - "description": "servers neither free, allocated or faulty with this size", + "description": "machine ids neither allocated nor waiting with this size", "items": { "type": "string" }, "type": "array" }, + "phoned_home": { + "description": "machines in phoned home provisioning state", + "format": "int32", + "type": "integer" + }, + "remainingreservations": { + "description": "the amount of unused / remaining / open reservations for this size", + "format": "int32", + "type": "integer" + }, "reservations": { "description": "the amount of reservations for this size", "format": "int32", "type": "integer" }, "size": { - "description": "the size of the server", + "description": "the size of the machine", "type": "string" }, "total": { - "description": "total amount of servers with this size", + "description": "total amount of machines with size", + "format": "int32", + "type": "integer" + }, + "unavailable": { + "description": "unavailable machines with this size", "format": "int32", "type": "integer" }, @@ -4493,19 +4551,15 @@ "description": "the amount of used reservations for this size", "format": "int32", "type": "integer" + }, + "waiting": { + "description": "machines in waiting provisioning state", + "format": "int32", + "type": "integer" } }, "required": [ - "allocated", - "faulty", - "faultymachines", - "free", - "other", - "othermachines", - "reservations", - "size", - "total", - "usedreservations" + "size" ] }, "v1.SizeConstraint": { @@ -4567,13 +4621,6 @@ "name": { "description": "a readable name for this entity", "type": "string" - }, - "reservations": { - "description": "reservations for this size, which are considered during machine allocation", - "items": { - "$ref": "#/definitions/v1.SizeReservation" - }, - "type": "array" } }, "required": [ @@ -4679,15 +4726,19 @@ "id" ] }, - "v1.SizeReservation": { + "v1.SizeReservationCreateRequest": { "properties": { "amount": { - "description": "the amount of reserved machine allocations for this size", + "description": "the amount of reservations of this size reservation", "format": "int32", "type": "integer" }, "description": { - "description": "a description for this reservation", + "description": "a description for this entity", + "type": "string" + }, + "id": { + "description": "the unique ID of this entity", "type": "string" }, "labels": { @@ -4697,26 +4748,40 @@ "description": "free labels associated with this size reservation.", "type": "object" }, + "name": { + "description": "a readable name for this entity", + "type": "string" + }, "partitionids": { - "description": "the partitions in which this size reservation is considered, the amount is valid for every partition", + "description": "the partition id of this size reservation", "items": { "type": "string" }, "type": "array" }, "projectid": { - "description": "the project for which this size reservation is considered", + "description": "the project id of this size reservation", + "type": "string" + }, + "sizeid": { + "description": "the size id of this size reservation", "type": "string" } }, "required": [ "amount", + "id", "partitionids", - "projectid" + "projectid", + "sizeid" ] }, "v1.SizeReservationListRequest": { "properties": { + "id": { + "description": "the id of this size reservation", + "type": "string" + }, "partitionid": { "description": "the partition id of this size reservation", "type": "string" @@ -4728,15 +4793,36 @@ "sizeid": { "description": "the size id of this size reservation", "type": "string" - }, - "tenant": { - "description": "the tenant of this size reservation", - "type": "string" } } }, "v1.SizeReservationResponse": { "properties": { + "amount": { + "description": "the amount of reservations of this size reservation", + "format": "int32", + "type": "integer" + }, + "changed": { + "description": "the last changed timestamp of this entity", + "format": "date-time", + "readOnly": true, + "type": "string" + }, + "created": { + "description": "the creation time of this entity", + "format": "date-time", + "readOnly": true, + "type": "string" + }, + "description": { + "description": "a description for this entity", + "type": "string" + }, + "id": { + "description": "the unique ID of this entity", + "type": "string" + }, "labels": { "additionalProperties": { "type": "string" @@ -4744,51 +4830,131 @@ "description": "free labels associated with this size reservation.", "type": "object" }, - "partitionid": { + "name": { + "description": "a readable name for this entity", + "type": "string" + }, + "partitionids": { "description": "the partition id of this size reservation", + "items": { + "type": "string" + }, + "type": "array" + }, + "projectid": { + "description": "the project id of this size reservation", "type": "string" }, - "projectallocations": { - "description": "the amount of allocations of this project referenced by this size reservation", + "sizeid": { + "description": "the size id of this size reservation", + "type": "string" + } + }, + "required": [ + "amount", + "id", + "partitionids", + "projectid", + "sizeid" + ] + }, + "v1.SizeReservationUpdateRequest": { + "properties": { + "amount": { + "description": "the amount of reservations of this size reservation", "format": "int32", "type": "integer" }, - "projectid": { - "description": "the project id of this size reservation", + "description": { + "description": "a description for this entity", "type": "string" }, - "projectname": { - "description": "the project name of this size reservation", + "id": { + "description": "the unique ID of this entity", "type": "string" }, - "reservations": { + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "free labels associated with this size reservation.", + "type": "object" + }, + "name": { + "description": "a readable name for this entity", + "type": "string" + }, + "partitionids": { + "description": "the partition id of this size reservation", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "amount", + "id", + "partitionids" + ] + }, + "v1.SizeReservationUsageResponse": { + "properties": { + "amount": { "description": "the amount of reservations of this size reservation", "format": "int32", "type": "integer" }, - "sizeid": { - "description": "the size id of this size reservation", + "description": { + "description": "a description for this entity", "type": "string" }, - "tenant": { - "description": "the tenant of this size reservation", + "id": { + "description": "the unique ID of this entity", "type": "string" }, - "usedreservations": { + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "free labels associated with this size reservation.", + "type": "object" + }, + "name": { + "description": "a readable name for this entity", + "type": "string" + }, + "partitionid": { + "description": "the partition id of this size reservation", + "type": "string" + }, + "projectallocations": { + "description": "the amount of allocations of this project referenced by this size reservation", + "format": "int32", + "type": "integer" + }, + "projectid": { + "description": "the project id of this size reservation", + "type": "string" + }, + "sizeid": { + "description": "the size id of this size reservation", + "type": "string" + }, + "usedamount": { "description": "the used amount of reservations of this size reservation", "format": "int32", "type": "integer" } }, "required": [ + "amount", + "id", "partitionid", "projectallocations", "projectid", - "projectname", - "reservations", "sizeid", - "tenant", - "usedreservations" + "usedamount" ] }, "v1.SizeResponse": { @@ -4830,13 +4996,6 @@ "name": { "description": "a readable name for this entity", "type": "string" - }, - "reservations": { - "description": "reservations for this size, which are considered during machine allocation", - "items": { - "$ref": "#/definitions/v1.SizeReservation" - }, - "type": "array" } }, "required": [ @@ -4883,13 +5042,6 @@ "name": { "description": "a readable name for this entity", "type": "string" - }, - "reservations": { - "description": "reservations for this size, which are considered during machine allocation", - "items": { - "$ref": "#/definitions/v1.SizeReservation" - }, - "type": "array" } }, "required": [ @@ -9110,11 +9262,129 @@ } }, "/v1/size/reservations": { - "post": { + "get": { "consumes": [ "application/json" ], "operationId": "listSizeReservations", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/v1.SizeReservationResponse" + }, + "type": "array" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "get all size reservations", + "tags": [ + "size" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "operationId": "updateSizeReservation", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.SizeReservationUpdateRequest" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.SizeReservationResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "updates a size reservation. if the size reservation was changed since this one was read, a conflict is returned", + "tags": [ + "size" + ] + }, + "put": { + "consumes": [ + "application/json" + ], + "operationId": "createSizeReservation", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.SizeReservationCreateRequest" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/v1.SizeReservationResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "create a size reservation. if the given ID already exists a conflict is returned", + "tags": [ + "size" + ] + } + }, + "/v1/size/reservations/find": { + "post": { + "consumes": [ + "application/json" + ], + "operationId": "findSizeReservations", "parameters": [ { "in": "body", @@ -9151,6 +9421,122 @@ ] } }, + "/v1/size/reservations/usage": { + "post": { + "consumes": [ + "application/json" + ], + "operationId": "sizeReservationsUsage", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.SizeReservationListRequest" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/v1.SizeReservationUsageResponse" + }, + "type": "array" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "get all size reservations", + "tags": [ + "size" + ] + } + }, + "/v1/size/reservations/{id}": { + "delete": { + "consumes": [ + "application/json" + ], + "operationId": "deleteSizeReservation", + "parameters": [ + { + "description": "identifier of the size reservation", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.SizeReservationResponse" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "deletes a size reservation and returns the deleted entity", + "tags": [ + "size" + ] + }, + "get": { + "consumes": [ + "application/json" + ], + "operationId": "getSizeReservation", + "parameters": [ + { + "description": "identifier of the size reservation", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.SizeReservationResponse" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "get size reservation by id", + "tags": [ + "size" + ] + } + }, "/v1/size/suggest": { "post": { "consumes": [