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

Commit

Permalink
feat: add field that dedicates a node to a certain user (#293)
Browse files Browse the repository at this point in the history
* feat: add field that dedicates a node to a certain user

* further improve node dedication

* update node create

* fix CI

* wip: dedicated nodes

* move the dedicated endpoint

* Only check dedication on zos workloads types

Co-authored-by: Muhamad Azamy <[email protected]>
  • Loading branch information
DylanVerstraete and muhamadazmy authored Mar 11, 2021
1 parent ae63824 commit 64302cf
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 2 deletions.
2 changes: 2 additions & 0 deletions models/generated/directory/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ type Node struct {
WgPorts []int64 `bson:"wg_ports" json:"wg_ports"`
Deleted bool `bson:"deleted" json:"deleted"`
Reserved bool `bson:"reserved" json:"reserved"`
// optional flag to indicate that a node can only accept workloads from a certain user
Dedicated schema.ID `bson:"dedicated" json:"dedicated"`
}

func NewNode() (Node, error) {
Expand Down
31 changes: 31 additions & 0 deletions models/generated/workloads/reservation.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ const (
WorkloadTypePublicIP
)

// Any returns true if the type is one of the given types
func (t WorkloadTypeEnum) Any(types ...WorkloadTypeEnum) bool {
for _, typ := range types {
if typ == t {
return true
}
}

return false
}

// WorkloadTypes is a map of all the supported workload type
var WorkloadTypes = map[WorkloadTypeEnum]string{
WorkloadTypeZDB: "zdb",
Expand All @@ -169,3 +180,23 @@ func (e WorkloadTypeEnum) String() string {
}
return s
}

// GatewayTypes is a list of all types processed by a gateway
var GatewayTypes = []WorkloadTypeEnum{
WorkloadTypeProxy,
WorkloadTypeReverseProxy,
WorkloadTypeSubDomain,
WorkloadTypeDomainDelegate,
WorkloadTypeGateway4To6,
}

// ZeroOSTypes is a list of all types supported by zero-os node
var ZeroOSTypes = []WorkloadTypeEnum{
WorkloadTypeZDB,
WorkloadTypeContainer,
WorkloadTypeVolume,
WorkloadTypeNetwork,
WorkloadTypeKubernetes,
WorkloadTypeNetworkResource,
WorkloadTypePublicIP,
}
48 changes: 48 additions & 0 deletions pkg/directory/node_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/threefoldtech/tfexplorer/mw"
"github.com/threefoldtech/tfexplorer/pkg/directory/types"
directory "github.com/threefoldtech/tfexplorer/pkg/directory/types"
phonebook "github.com/threefoldtech/tfexplorer/pkg/phonebook/types"
"github.com/threefoldtech/tfexplorer/schema"
"github.com/threefoldtech/zos/pkg/capacity"
"github.com/threefoldtech/zos/pkg/capacity/dmi"
Expand Down Expand Up @@ -56,6 +57,7 @@ func (s *NodeAPI) registerNode(r *http.Request) (interface{}, mw.Response) {
n.PublicConfig = nil
// and it not immediately deleted
n.Deleted = false

if _, err := s.Add(r.Context(), db, n); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return nil, mw.NotFound(fmt.Errorf("farm with id:%d does not exists", n.FarmId))
Expand Down Expand Up @@ -158,6 +160,52 @@ func (s *NodeAPI) registerIfaces(r *http.Request) (interface{}, mw.Response) {
return nil, mw.Created()
}

func (s *NodeAPI) setNodeDedicated(r *http.Request) (interface{}, mw.Response) {
data := struct {
UserID int64 `json:"user_id"`
}{}

if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
return nil, mw.BadRequest(err)
}

db := mw.Database(r)
nodeID := mux.Vars(r)["node_id"]

node, err := s.Get(r.Context(), db, nodeID, false)
if err != nil {
return nil, mw.NotFound(err)
}

// ensure it is the farmer that does the call
authorized, merr := isFarmerAuthorized(r, node, db)
if err != nil {
return nil, merr
}

if !authorized {
return nil, mw.Forbidden(fmt.Errorf("only the farmer can configured the public interface of its nodes"))
}

// if a userID is provided we check if the user exists
// otherwise it will be an unset operation
if data.UserID > 0 {
var filter phonebook.UserFilter
filter = filter.WithID(schema.ID(data.UserID))
_, err = filter.Get(r.Context(), db)
if err != nil {
return nil, mw.NotFound(errors.Wrap(err, "user not found"))
}
}

err = directory.NodeUpdateDedicated(r.Context(), db, nodeID, data.UserID)
if err != nil {
return nil, mw.Error(err)
}

return nil, mw.Ok()
}

func (s *NodeAPI) configurePublic(r *http.Request) (interface{}, mw.Response) {
var iface generated.PublicIface

Expand Down
2 changes: 2 additions & 0 deletions pkg/directory/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func Setup(parent *mux.Router, db *mongo.Database) error {
farmsAuthenticated.HandleFunc("/ip", mw.AsHandlerFunc(farmAPI.deleteFarmIps)).Methods("DELETE").Name("farm-delete-ip-v1")
farmsAuthenticated.HandleFunc("", mw.AsHandlerFunc(farmAPI.updateFarm)).Methods("PUT").Name("farm-update-v1")
farmsAuthenticated.HandleFunc("/{node_id}", mw.AsHandlerFunc(nodeAPI.Requires("node_id", farmAPI.deleteNodeFromFarm))).Methods("DELETE").Name("farm-node-delete-v1")

farmsAuthenticated.HandleFunc("/deals", mw.AsHandlerFunc(farmAPI.createOrUpdateFarmCustomPrice)).Methods("POST", "PUT").Name("farm-update-prices-v1")
farmsAuthenticated.HandleFunc("/deals/{threebot_id}", mw.AsHandlerFunc(farmAPI.deleteFarmCustomPrice)).Methods("DELETE").Name("farm-delete-prices-v1")

Expand All @@ -60,6 +61,7 @@ func Setup(parent *mux.Router, db *mongo.Database) error {
nodesAuthenticated.HandleFunc("/{node_id}/interfaces", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerIfaces))).Methods("POST").Name("node-interfaces-v1")
nodesAuthenticated.HandleFunc("/{node_id}/ports", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerPorts))).Methods("POST").Name("node-set-ports-v1")
userAuthenticated.HandleFunc("/{node_id}/configure_public", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.configurePublic))).Methods("POST").Name("node-configure-public-v1")
userAuthenticated.HandleFunc("/{node_id}/dedicated", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.setNodeDedicated))).Methods("PUT").Name("node-set-node-dedicated")
userAuthenticated.HandleFunc("/{node_id}/configure_free", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.configureFreeToUse))).Methods("POST").Name("node-configure-free-v1")
nodesAuthenticated.HandleFunc("/{node_id}/capacity", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerCapacity))).Methods("POST").Name("node-capacity-v1")
nodesAuthenticated.HandleFunc("/{node_id}/uptime", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.updateUptimeHandler))).Methods("POST").Name("node-uptime-v1")
Expand Down
7 changes: 7 additions & 0 deletions pkg/directory/types/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ func NodeSetWGPorts(ctx context.Context, db *mongo.Database, nodeID string, port
})
}

// NodeUpdateDedicated sets node dedicated user id
func NodeUpdateDedicated(ctx context.Context, db *mongo.Database, nodeID string, dedicated int64) error {
return nodeUpdate(ctx, db, nodeID, bson.M{
"dedicated": dedicated,
})
}

// NodePushProof push proof to node
func NodePushProof(ctx context.Context, db *mongo.Database, nodeID string, proof generated.Proof) error {
if nodeID == "" {
Expand Down
23 changes: 21 additions & 2 deletions pkg/workloads/reservation.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ func (a *API) create(r *http.Request) (interface{}, mw.Response) {
return nil, mw.UnAuthorized(fmt.Errorf("request user identity does not match the reservation customer-tid"))
}

db := mw.Database(r)

typ := workload.GetWorkloadType()
if typ.Any(generated.ZeroOSTypes...) {
// dedication does not apply on gateways, hence this
// check is here
var nodeFilter directory.NodeFilter
nodeFilter = nodeFilter.WithNodeID(workload.GetNodeID())
node, err := nodeFilter.Get(r.Context(), db, false)
if err != nil {
return nil, mw.BadRequest(errors.Wrapf(err, "cannot find node with id '%s'", workload.GetNodeID()))
}

// if a node has a dedicated ID assigned it means it will only take reservations from this user
if node.Dedicated != 0 {
if workload.GetCustomerTid() != int64(node.Dedicated) {
return nil, mw.UnAuthorized(fmt.Errorf("node accepts only reservations from user with id: %d", node.Dedicated))
}
}
}

workload, err = a.workloadpipeline(workload, nil)
if err != nil {
// if failed to create pipeline, then
Expand All @@ -111,8 +132,6 @@ func (a *API) create(r *http.Request) (interface{}, mw.Response) {
return nil, mw.BadRequest(fmt.Errorf("invalid request wrong status '%s'", workload.GetNextAction().String()))
}

db := mw.Database(r)

var filter phonebook.UserFilter
filter = filter.WithID(schema.ID(workload.GetCustomerTid()))
user, err := filter.Get(r.Context(), db)
Expand Down

0 comments on commit 64302cf

Please sign in to comment.