Skip to content
This repository has been archived by the owner on Feb 15, 2023. It is now read-only.

Commit

Permalink
Make a public ip reservation atomic swapable (#319)
Browse files Browse the repository at this point in the history
* ip reservation node swap

* check against pool id not customer id

* use pool id in filter

Co-authored-by: Muhamad Azamy <[email protected]>
  • Loading branch information
DylanVerstraete and muhamadazmy authored Jun 1, 2021
1 parent 1ce2486 commit e68d9e0
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
11 changes: 8 additions & 3 deletions pkg/directory/types/farm.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,19 +253,24 @@ func FarmUpdate(ctx context.Context, db *mongo.Database, id schema.ID, farm Farm

// FarmIPReserve reserves an IP if it's only free
func FarmIPReserve(ctx context.Context, db *mongo.Database, farm schema.ID, ip schema.IPCidr, reservation schema.ID) error {
return FarmIPSwap(ctx, db, farm, ip, 0, reservation)
}

// FarmIPSwap swaps ip reservation id atomically
func FarmIPSwap(ctx context.Context, db *mongo.Database, farm schema.ID, ip schema.IPCidr, from, to schema.ID) error {
col := db.Collection(FarmCollection)
// filter using 0 reservation id (not reserved)
filter := FarmFilter{}.WithID(farm).WithIP(ip, 0)
filter := FarmFilter{}.WithID(farm).WithIP(ip, from)

results, err := col.UpdateOne(ctx, filter, bson.M{
"$set": bson.M{"ipaddresses.$.reservation_id": reservation},
"$set": bson.M{"ipaddresses.$.reservation_id": to},
})
if err != nil {
return err
}

if results.ModifiedCount != 1 {
return fmt.Errorf("ip is not available for reservation")
return fmt.Errorf("ip reservation swap failed")
}

return nil
Expand Down
44 changes: 43 additions & 1 deletion pkg/workloads/reservation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/threefoldtech/tfexplorer/models"
generateddirectory "github.com/threefoldtech/tfexplorer/models/generated/directory"
"github.com/threefoldtech/tfexplorer/models/generated/workloads"
generated "github.com/threefoldtech/tfexplorer/models/generated/workloads"
"github.com/threefoldtech/tfexplorer/mw"
Expand Down Expand Up @@ -1278,6 +1279,10 @@ func (a *API) setWorkloadDelete(ctx context.Context, db *mongo.Database, w types
}

func (a *API) handlePublicIPReservation(ctx context.Context, db *mongo.Database, workload types.WorkloaderType) mw.Response {
// handling ip reservation is very special because
// 1- It's mostly handled by the explorer itself
// 2- it should be possible for the IP owner to move the IP reservation to another node (swap)

ipWorkload := workload.Workloader.(*generated.PublicIP)

var nodeFilter directory.NodeFilter
Expand All @@ -1294,7 +1299,44 @@ func (a *API) handlePublicIPReservation(ctx context.Context, db *mongo.Database,
return mw.BadRequest(errors.Wrap(err, "failed to retrieve farm"))
}

if err := directory.FarmIPReserve(ctx, db, farm.ID, ipWorkload.IPaddress, workload.GetID()); err != nil {
var pubIP *generateddirectory.PublicIP
for i := range farm.IPAddresses {
ip := &farm.IPAddresses[i]

if ip.Address.IP.Equal(ipWorkload.IPaddress.IP) {
pubIP = ip
break
}
}

if pubIP == nil {
// no ip found
return mw.NotFound(fmt.Errorf("public ip not found in farm"))
}

swap := pubIP.ReservationID
// if swap != 0 then the ip is already allocated to 'someone'
if swap != 0 {
// the owner if the reservation can then be someone else or the same owner
var filter types.WorkloadFilter
filter = filter.WithID(swap).
WithPoolID(ipWorkload.PoolId).
WithNextAction(generated.NextActionDeploy)

wl, err := filter.Get(ctx, db)
if errors.Is(err, mongo.ErrNoDocuments) {
// this reservation is owned by another user!! we can't do swap
return mw.Conflict(fmt.Errorf("ip address already in use by another pool"))
}

// same user, we need to deprovision this one
if _, err := a.setWorkloadDelete(ctx, db, wl); err != nil {
return mw.Error(errors.Wrap(err, "failed to schedule ip reservation to be deleted"))
}
}

// swap will atomically swap the reservation on the IP address against
if err := directory.FarmIPSwap(ctx, db, farm.ID, ipWorkload.IPaddress, swap, workload.GetID()); err != nil {
return mw.Conflict(err)
}

Expand Down

0 comments on commit e68d9e0

Please sign in to comment.