Repository: threefoldtech/zos
Pull request #579 (2020-03-17 08:10:12)

Mock tfuser fixes


Patch:
diff --git a/pkg/gedis/commands_provision.go b/pkg/gedis/commands_provision.go
index fa8b4410..aa144b3e 100644
--- a/pkg/gedis/commands_provision.go
+++ b/pkg/gedis/commands_provision.go
@@ -126,7 +126,7 @@ func (g *Gedis) Feedback(nodeID string, r *provision.Result) error {
 	result := ptypes.TfgridReservationResult1{
 		Category:   rType,
 		WorkloadID: r.ID,
-		DataJSON:   string(r.Data),
+		DataJSON:   r.Data,
 		Signature:  r.Signature,
 		State:      ptypes.TfgridReservationResult1StateEnum(r.State),
 		Message:    r.Error,
diff --git a/pkg/gedis/converter.go b/pkg/gedis/converter.go
index 4da83f7c..d276aa06 100644
--- a/pkg/gedis/converter.go
+++ b/pkg/gedis/converter.go
@@ -120,14 +120,15 @@ func networkReservation(i interface{}) types.TfgridReservationNetwork1 {
 func containerReservation(i interface{}, nodeID string) types.TfgridReservationContainer1 {
 	c := i.(provision.Container)
 	container := types.TfgridReservationContainer1{
-		NodeID:      nodeID,
-		WorkloadID:  1,
-		Flist:       c.FList,
-		HubURL:      c.FlistStorage,
-		Environment: c.Env,
-		Entrypoint:  c.Entrypoint,
-		Interactive: c.Interactive,
-		Volumes:     make([]types.TfgridReservationContainerMount1, len(c.Mounts)),
+		NodeID:            nodeID,
+		WorkloadID:        1,
+		Flist:             c.FList,
+		HubURL:            c.FlistStorage,
+		Environment:       c.Env,
+		SecretEnvironment: c.SecretEnv,
+		Entrypoint:        c.Entrypoint,
+		Interactive:       c.Interactive,
+		Volumes:           make([]types.TfgridReservationContainerMount1, len(c.Mounts)),
 		NetworkConnection: []types.TfgridReservationNetworkConnection1{
 			{
 				NetworkID: string(c.Network.NetworkID),
diff --git a/pkg/gedis/types/provision/tfgrid_reservation_1.go b/pkg/gedis/types/provision/tfgrid_reservation_1.go
index 78bf3a38..30d7556d 100644
--- a/pkg/gedis/types/provision/tfgrid_reservation_1.go
+++ b/pkg/gedis/types/provision/tfgrid_reservation_1.go
@@ -1,13 +1,15 @@
 package provision
 
 import (
+	"encoding/json"
+
 	schema "github.com/threefoldtech/zos/pkg/schema"
 )
 
 // TfgridReservation1 jsx schema
 type TfgridReservation1 struct {
 	ID                  uint                                 `json:"id"`
-	JSON                string                               `json:"json"`
+	JSON                json.RawMessage                      `json:"json"`
 	DataReservation     TfgridReservationData1               `json:"data_reservation"`
 	CustomerTid         int64                                `json:"customer_tid"`
 	CustomerSignature   string                               `json:"customer_signature"`
diff --git a/pkg/gedis/types/provision/tfgrid_reservation_statsaggregator_1.go b/pkg/gedis/types/provision/tfgrid_reservation_statsaggregator_1.go
index 5a19d8bd..44f98200 100644
--- a/pkg/gedis/types/provision/tfgrid_reservation_statsaggregator_1.go
+++ b/pkg/gedis/types/provision/tfgrid_reservation_statsaggregator_1.go
@@ -1,6 +1,8 @@
 package provision
 
 import (
+	"encoding/json"
+
 	schema "github.com/threefoldtech/zos/pkg/schema"
 )
 
@@ -15,7 +17,7 @@ type TfgridReservationStatsaggregator1 struct {
 type TfgridReservationResult1 struct {
 	Category   TfgridReservationResult1CategoryEnum `json:"category"`
 	WorkloadID string                               `json:"workload_id"`
-	DataJSON   string                               `json:"data_json"`
+	DataJSON   json.RawMessage                      `json:"data_json"`
 	Signature  string                               `json:"signature"`
 	State      TfgridReservationResult1StateEnum    `json:"state"`
 	Message    string                               `json:"message"`
diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go
index 16531cc1..6971c613 100644
--- a/pkg/provision/engine.go
+++ b/pkg/provision/engine.go
@@ -151,7 +151,7 @@ func (e *defaultEngine) decommission(ctx context.Context, r *Reservation) error
 
 	if !exists {
 		log.Info().Str("id", r.ID).Msg("reservation not provisioned, no need to decomission")
-		if err := e.fb.Deleted(r.NodeID, r.ID); err != nil {
+		if err := e.fb.Deleted(e.nodeID, r.ID); err != nil {
 			log.Error().Err(err).Str("id", r.ID).Msg("failed to mark reservation as deleted")
 		}
 		return nil
@@ -166,7 +166,7 @@ func (e *defaultEngine) decommission(ctx context.Context, r *Reservation) error
 		return errors.Wrapf(err, "failed to remove reservation %s from cache", r.ID)
 	}
 
-	if err := e.fb.Deleted(r.NodeID, r.ID); err != nil {
+	if err := e.fb.Deleted(e.nodeID, r.ID); err != nil {
 		return errors.Wrap(err, "failed to mark reservation as deleted")
 	}
 
diff --git a/tools/bcdb_mock/models/generated/workloads/workloads.go b/tools/bcdb_mock/models/generated/workloads/workloads.go
index 7691b209..83457705 100644
--- a/tools/bcdb_mock/models/generated/workloads/workloads.go
+++ b/tools/bcdb_mock/models/generated/workloads/workloads.go
@@ -9,7 +9,7 @@ import (
 
 type TfgridWorkloadsReservation1 struct {
 	ID                  schema.ID                                     `bson:"_id" json:"id"`
-	Json                string                                        `bson:"json" json:"json"`
+	Json                json.RawMessage                               `bson:"json" json:"json"`
 	DataReservation     TfgridWorkloadsReservationData1               `bson:"data_reservation" json:"data_reservation"`
 	CustomerTid         int64                                         `bson:"customer_tid" json:"customer_tid"`
 	CustomerSignature   string                                        `bson:"customer_signature" json:"customer_signature"`
diff --git a/tools/bcdb_mock/pkg/workloads/reservation.go b/tools/bcdb_mock/pkg/workloads/reservation.go
index 6dccff13..82bcf8fd 100644
--- a/tools/bcdb_mock/pkg/workloads/reservation.go
+++ b/tools/bcdb_mock/pkg/workloads/reservation.go
@@ -161,7 +161,7 @@ func (a *API) queued(ctx context.Context, db *mongo.Database, nodeID string, lim
 		WorkloadID string                                                `bson:"workload_id" json:"workload_id"`
 		User       string                                                `bson:"user" json:"user"`
 		Type       generated.TfgridWorkloadsReservationWorkload1TypeEnum `bson:"type" json:"type"`
-		Content    bson.M                                                `bson:"content" json:"content"`
+		Content    bson.Raw                                              `bson:"content" json:"content"`
 		Created    schema.Date                                           `bson:"created" json:"created"`
 		Duration   int64                                                 `bson:"duration" json:"duration"`
 		Signature  string                                                `bson:"signature" json:"signature"`
@@ -191,18 +191,57 @@ func (a *API) queued(ctx context.Context, db *mongo.Database, nodeID string, lim
 		if err := cur.Decode(&wl); err != nil {
 			return workloads, err
 		}
+
+		obj := generated.TfgridWorkloadsReservationWorkload1{
+			WorkloadId: wl.WorkloadID,
+			User:       wl.User,
+			Type:       wl.Type,
+			// Content:    wl.Content,
+			Created:   wl.Created,
+			Duration:  wl.Duration,
+			Signature: wl.Signature,
+			ToDelete:  wl.ToDelete,
+		}
+		switch wl.Type {
+		case generated.TfgridWorkloadsReservationWorkload1TypeContainer:
+			var data generated.TfgridWorkloadsReservationContainer1
+			if err := bson.Unmarshal(wl.Content, &data); err != nil {
+				return nil, err
+			}
+			obj.Content = data
+
+		case generated.TfgridWorkloadsReservationWorkload1TypeVolume:
+			var data generated.TfgridWorkloadsReservationVolume1
+			if err := bson.Unmarshal(wl.Content, &data); err != nil {
+				return nil, err
+			}
+			obj.Content = data
+
+		case generated.TfgridWorkloadsReservationWorkload1TypeZdb:
+			var data generated.TfgridWorkloadsReservationZdb1
+			if err := bson.Unmarshal(wl.Content, &data); err != nil {
+				return nil, err
+			}
+			obj.Content = data
+
+		case generated.TfgridWorkloadsReservationWorkload1TypeNetwork:
+			var data generated.TfgridWorkloadsReservationNetwork1
+			if err := bson.Unmarshal(wl.Content, &data); err != nil {
+				return nil, err
+			}
+			obj.Content = data
+
+		case generated.TfgridWorkloadsReservationWorkload1TypeKubernetes:
+			var data generated.TfgridWorkloadsReservationK8S1
+			if err := bson.Unmarshal(wl.Content, &data); err != nil {
+				return nil, err
+			}
+			obj.Content = data
+		}
+
 		workloads = append(workloads, types.Workload{
-			NodeID: wl.NodeID,
-			TfgridWorkloadsReservationWorkload1: generated.TfgridWorkloadsReservationWorkload1{
-				WorkloadId: wl.WorkloadID,
-				User:       wl.User,
-				Type:       wl.Type,
-				Content:    wl.Content,
-				Created:    wl.Created,
-				Duration:   wl.Duration,
-				Signature:  wl.Signature,
-				ToDelete:   wl.ToDelete,
-			},
+			NodeID:                              wl.NodeID,
+			TfgridWorkloadsReservationWorkload1: obj,
 		})
 	}
 
diff --git a/tools/bcdb_mock/pkg/workloads/types/reservation.go b/tools/bcdb_mock/pkg/workloads/types/reservation.go
index 3f7b810e..d775cac0 100644
--- a/tools/bcdb_mock/pkg/workloads/types/reservation.go
+++ b/tools/bcdb_mock/pkg/workloads/types/reservation.go
@@ -203,7 +203,7 @@ func (r *Reservation) SignatureVerify(pk string, sig []byte) error {
 		return errors.Wrap(err, "failed to write id to buffer")
 	}
 
-	if _, err := buf.WriteString(r.Json); err != nil {
+	if _, err := buf.Write(r.Json); err != nil {
 		return errors.Wrap(err, "failed to write json to buffer")
 	}
 
diff --git a/tools/tfuser/cmds_container.go b/tools/tfuser/cmds_container.go
index 6d0d50b4..a82ceb2f 100644
--- a/tools/tfuser/cmds_container.go
+++ b/tools/tfuser/cmds_container.go
@@ -49,7 +49,7 @@ func generateContainer(c *cli.Context) error {
 		return err
 	}
 
-	p, err := embed(container, provision.ContainerReservation)
+	p, err := embed(container, provision.ContainerReservation, c.String("node"))
 	if err != nil {
 		return err
 	}
diff --git a/tools/tfuser/cmds_debug.go b/tools/tfuser/cmds_debug.go
index 46dfc367..f3670ed8 100644
--- a/tools/tfuser/cmds_debug.go
+++ b/tools/tfuser/cmds_debug.go
@@ -26,7 +26,7 @@ func generateDebug(c *cli.Context) error {
 		Channel: c.String("channel"),
 	}
 
-	r, err := embed(s, provision.DebugReservation)
+	r, err := embed(s, provision.DebugReservation, c.String("node"))
 	if err != nil {
 		return err
 	}
diff --git a/tools/tfuser/cmds_kubernetes.go b/tools/tfuser/cmds_kubernetes.go
index 0a77f0c6..6c72be7b 100644
--- a/tools/tfuser/cmds_kubernetes.go
+++ b/tools/tfuser/cmds_kubernetes.go
@@ -68,7 +68,7 @@ func generateKubernetes(c *cli.Context) error {
 		SSHKeys:       sshKeys,
 	}
 
-	p, err := embed(kube, provision.KubernetesReservation)
+	p, err := embed(kube, provision.KubernetesReservation, c.String("node"))
 	if err != nil {
 		return errors.Wrap(err, "could not generate reservation schema")
 	}
diff --git a/tools/tfuser/cmds_live.go b/tools/tfuser/cmds_live.go
index 0780b1ed..925ec235 100644
--- a/tools/tfuser/cmds_live.go
+++ b/tools/tfuser/cmds_live.go
@@ -6,27 +6,27 @@ import (
 	"net/http"
 	"os"
 	"sync"
+	"time"
+
+	generated "github.com/threefoldtech/zos/pkg/gedis/types/provision"
 
-	"github.com/pkg/errors"
-	"github.com/threefoldtech/zos/pkg"
-	"github.com/threefoldtech/zos/pkg/identity"
 	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/urfave/cli"
 )
 
 func cmdsLive(c *cli.Context) error {
 	var (
-		seedPath = c.String("seed")
-		start    = c.Int("start")
-		end      = c.Int("end")
-		expired  = c.Bool("expired")
-		deleted  = c.Bool("deleted")
+		userID  = c.Int64("id")
+		start   = c.Int("start")
+		end     = c.Int("end")
+		expired = c.Bool("expired")
+		deleted = c.Bool("deleted")
 	)
 
-	keypair, err := identity.LoadKeyPair(seedPath)
-	if err != nil {
-		return errors.Wrapf(err, "could not find seed file at %s", seedPath)
-	}
+	// keypair, err := identity.LoadKeyPair(seedPath)
+	// if err != nil {
+	// 	return errors.Wrapf(err, "could not find seed file at %s", seedPath)
+	// }
 
 	s := scraper{
 		poolSize: 10,
@@ -35,8 +35,7 @@ func cmdsLive(c *cli.Context) error {
 		expired:  expired,
 		deleted:  deleted,
 	}
-
-	cResults := s.Scrap(keypair.Identity())
+	cResults := s.Scrap(userID)
 	for result := range cResults {
 		printResult(result)
 	}
@@ -45,40 +44,37 @@ func cmdsLive(c *cli.Context) error {
 
 const timeLayout = "02-Jan-2006 15:04:05"
 
-func printResult(r res) {
-	expire := r.Created.Add(r.Duration)
-	fmt.Printf("ID:%6s Type:%10s expired at:%20s", r.ID, r.Type, expire.Format(timeLayout))
-	if r.Result == nil {
+func printResult(r generated.TfgridReservation1) {
+	expire := r.DataReservation.ExpirationReservation
+	fmt.Printf("ID:%6d expired at:%20s", r.ID, expire.Format(timeLayout))
+
+	if len(r.Results) <= 0 {
 		fmt.Printf("state: not deployed yet\n")
 		return
 	}
-	fmt.Printf("state: %6s", r.Result.State)
-	if r.Result.State == provision.StateError {
-		fmt.Printf("\t%s\n", r.Result.Error)
-		return
+
+	resultPerID := make(map[int64]generated.TfgridReservationResult1, len(r.Results))
+	for _, r := range r.Results {
+		var (
+			rid int64
+			wid int64
+		)
+		fmt.Sscanf(r.WorkloadID, "%d-%d", &rid, &wid)
+		resultPerID[wid] = r
 	}
 
-	switch r.Type {
-	case provision.VolumeReservation:
-		rData := provision.VolumeResult{}
-		data := provision.Volume{}
-		if err := json.Unmarshal(r.Data, &data); err != nil {
-			panic(err)
-		}
-		if err := json.Unmarshal(r.Result.Data, &rData); err != nil {
-			panic(err)
-		}
-		fmt.Printf("\tVolume ID: %s Size: %d Type: %s\n", rData.ID, data.Size, data.Type)
-	case provision.ZDBReservation:
-		data := provision.ZDBResult{}
-		if err := json.Unmarshal(r.Result.Data, &data); err != nil {
-			panic(err)
+	for _, n := range r.DataReservation.Networks {
+		fmt.Printf("\tnetwork ID: %s\n", n.Name)
+	}
+	for _, c := range r.DataReservation.Containers {
+		result := resultPerID[c.WorkloadID]
+		if result.State == generated.TfgridReservationResult1StateError {
+			fmt.Printf("\terror: %s\n", result.Message)
+			continue
 		}
-		fmt.Printf("\tAddr %s:%d Namespace %s\n", data.IP, data.Port, data.Namespace)
 
-	case provision.ContainerReservation:
 		data := provision.Container{}
-		if err := json.Unmarshal(r.Data, &data); err != nil {
+		if err := json.Unmarshal(result.DataJSON, &data); err != nil {
 			panic(err)
 		}
 		fmt.Printf("\tflist: %s", data.FList)
@@ -86,17 +82,42 @@ func printResult(r res) {
 			fmt.Printf("\tIP: %s", ip)
 		}
 		fmt.Printf("\n")
-	case provision.NetworkReservation:
-		data := pkg.Network{}
-		if err := json.Unmarshal(r.Data, &data); err != nil {
+	}
+	for _, v := range r.DataReservation.Volumes {
+		result := resultPerID[v.WorkloadID]
+		if result.State == generated.TfgridReservationResult1StateError {
+			fmt.Printf("\terror: %s\n", result.Message)
+			continue
+		}
+
+		data := provision.VolumeResult{}
+		if err := json.Unmarshal(result.DataJSON, &data); err != nil {
 			panic(err)
 		}
+		fmt.Printf("\tVolume ID: %s Size: %d Type: %s\n", data.ID, v.Size, v.Type)
+	}
+	for _, z := range r.DataReservation.Zdbs {
+		result := resultPerID[z.WorkloadID]
+		if result.State == generated.TfgridReservationResult1StateError {
+			fmt.Printf("\terror: %s\n", result.Message)
+			continue
+		}
 
-		fmt.Printf("\tnetwork ID: %s\n", data.Name)
+		data := provision.ZDBResult{}
+		if err := json.Unmarshal(result.DataJSON, &data); err != nil {
+			panic(err)
+		}
+		fmt.Printf("\tAddr %s:%d Namespace %s\n", data.IP, data.Port, data.Namespace)
+	}
+	for _, k := range r.DataReservation.Kubernetes {
+		result := resultPerID[k.WorkloadID]
+		if result.State == generated.TfgridReservationResult1StateError {
+			fmt.Printf("\terror: %s\n", result.Message)
+			continue
+		}
 
-	case provision.KubernetesReservation:
 		data := provision.Kubernetes{}
-		if err := json.Unmarshal(r.Data, &data); err != nil {
+		if err := json.Unmarshal(result.DataJSON, &data); err != nil {
 			panic(err)
 		}
 
@@ -119,20 +140,16 @@ type scraper struct {
 }
 type job struct {
 	id      int
-	user    string
+	user    int64
 	expired bool
 	deleted bool
 }
-type res struct {
-	provision.Reservation
-	Result *provision.Result `json:"result"`
-}
 
-func (s *scraper) Scrap(user string) chan res {
+func (s *scraper) Scrap(user int64) chan generated.TfgridReservation1 {
 
 	var (
 		cIn  = make(chan job)
-		cOut = make(chan res)
+		cOut = make(chan generated.TfgridReservation1)
 	)
 
 	s.wg.Add(s.poolSize)
@@ -161,51 +178,55 @@ func (s *scraper) Scrap(user string) chan res {
 	return cOut
 }
 
-func worker(wg *sync.WaitGroup, cIn <-chan job, cOut chan<- res) {
+func worker(wg *sync.WaitGroup, cIn <-chan job, cOut chan<- generated.TfgridReservation1) {
 	defer func() {
 		wg.Done()
 	}()
 
+	now := time.Now()
+
 	for job := range cIn {
 		res, err := getResult(job.id)
 		if err != nil {
 			continue
 		}
 
-		if !job.expired && res.Expired() {
+		expired := now.After(res.DataReservation.ExpirationReservation.Time)
+
+		if !job.expired && expired {
 			continue
 		}
-		if !job.deleted && res.ToDelete {
+		// FIXME: beurk
+		if !job.deleted && len(res.Results) > 0 && res.Results[0].State == generated.TfgridReservationResult1StateDeleted {
 			continue
 		}
-		if res.User != job.user {
+		if res.CustomerTid != job.user {
 			continue
 		}
 		cOut <- res
 	}
 }
 
-func getResult(id int) (res, error) {
-	url := fmt.Sprintf("https://explorer.devnet.grid.tf/reservations/%d-1", id)
+func getResult(id int) (res generated.TfgridReservation1, err error) {
+	url := fmt.Sprintf("https://explorer.devnet.grid.tf/reservations/%d", id)
 	resp, err := http.Get(url)
 	if err != nil {
-		return res{}, err
+		return res, err
 	}
 
 	defer resp.Body.Close()
 
 	if resp.StatusCode == http.StatusNotFound {
-		return res{}, os.ErrNotExist
+		return res, os.ErrNotExist
 	}
 
 	if resp.StatusCode != http.StatusOK {
-		return res{}, fmt.Errorf("wrong status code %s", resp.Status)
+		return res, fmt.Errorf("wrong status code %s", resp.Status)
 	}
 
-	b := res{}
-	if err := json.NewDecoder(resp.Body).Decode(&b); err != nil {
-		return res{}, err
+	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
+		return res, err
 	}
 
-	return b, nil
+	return res, nil
 }
diff --git a/tools/tfuser/cmds_network.go b/tools/tfuser/cmds_network.go
index 6744f6a3..1d83c3b3 100644
--- a/tools/tfuser/cmds_network.go
+++ b/tools/tfuser/cmds_network.go
@@ -69,7 +69,7 @@ func cmdCreateNetwork(c *cli.Context) error {
 		NetResources: []pkg.NetResource{},
 	}
 
-	r, err := embed(network, provision.NetworkReservation)
+	r, err := embed(network, provision.NetworkReservation, "")
 	if err != nil {
 		return err
 	}
@@ -158,7 +158,7 @@ func cmdsAddNode(c *cli.Context) error {
 		return errors.Wrap(err, "failed to generate peers")
 	}
 
-	r, err := embed(pkgNetFromNetwork(*network), provision.NetworkReservation)
+	r, err := embed(pkgNetFromNetwork(*network), provision.NetworkReservation, "")
 	if err != nil {
 		return err
 	}
@@ -268,7 +268,7 @@ func cmdsAddAccess(c *cli.Context) error {
 
 	fmt.Println(wgConf)
 
-	r, err := embed(pkgNetFromNetwork(*network), provision.NetworkReservation)
+	r, err := embed(pkgNetFromNetwork(*network), provision.NetworkReservation, "")
 	if err != nil {
 		return err
 	}
diff --git a/tools/tfuser/cmds_provision.go b/tools/tfuser/cmds_provision.go
index ae610f87..ab11e685 100644
--- a/tools/tfuser/cmds_provision.go
+++ b/tools/tfuser/cmds_provision.go
@@ -91,7 +91,6 @@ func cmdsProvision(c *cli.Context) error {
 	var (
 		schema   []byte
 		path     = c.String("schema")
-		nodeIDs  = c.StringSlice("node")
 		seedPath = c.String("seed")
 		d        = c.String("duration")
 		userID   = c.Int64("id")
@@ -136,60 +135,63 @@ func cmdsProvision(c *cli.Context) error {
 	// set the user ID into the reservation schema
 	//reservation.User = keypair.Identity()
 
-	for _, nodeID := range nodeIDs {
-		r := reservation //make a copy
-		r.NodeID = nodeID
-
-		custom, ok := provCustomModifiers[r.Type]
-		if ok {
-			if err := custom(&r); err != nil {
-				return err
-			}
+	custom, ok := provCustomModifiers[reservation.Type]
+	fmt.Println("customization: ", ok)
+	if ok {
+		fmt.Println("running customization function", reservation.NodeID)
+		if err := custom(&reservation); err != nil {
+			return err
 		}
+	}
 
-		jsx, err := gedis.ReservationToSchemaType(&r)
-		if err != nil {
-			return errors.Wrap(err, "failed to convert reservation to schema type")
-		}
-		jsx.CustomerTid = userID
-		// we always allow user to delete his own reservations
-		jsx.DataReservation.SigningRequestDelete.QuorumMin = 1
-		jsx.DataReservation.SigningRequestDelete.Signers = []int64{userID}
+	jsx, err := gedis.ReservationToSchemaType(&reservation)
+	if err != nil {
+		return errors.Wrap(err, "failed to convert reservation to schema type")
+	}
+	jsx.CustomerTid = userID
+	// we always allow user to delete his own reservations
+	jsx.DataReservation.SigningRequestDelete.QuorumMin = 1
+	jsx.DataReservation.SigningRequestDelete.Signers = []int64{userID}
 
-		bytes, err := json.Marshal(jsx.DataReservation)
-		if err != nil {
-			return err
-		}
+	bytes, err := json.Marshal(jsx.DataReservation)
+	if err != nil {
+		return err
+	}
 
-		jsx.JSON = string(bytes)
-		signature, err := crypto.Sign(keypair.PrivateKey, []byte(jsx.JSON))
-		if err != nil {
-			return errors.Wrap(err, "failed to sign the reservation")
-		}
+	jsx.JSON = bytes
+	signature, err := crypto.Sign(keypair.PrivateKey, []byte(jsx.JSON))
+	if err != nil {
+		return errors.Wrap(err, "failed to sign the reservation")
+	}
 
-		jsx.CustomerSignature = hex.EncodeToString(signature)
+	jsx.CustomerSignature = hex.EncodeToString(signature)
 
-		id, err := client.ReserveJSX(jsx)
-		if err != nil {
-			return errors.Wrap(err, "failed to send reservation")
-		}
+	if c.Bool("dry-run") {
+		enc := json.NewEncoder(os.Stdout)
+		enc.SetIndent("", "  ")
+		return enc.Encode(jsx)
+	}
 
-		fmt.Printf("Reservation for %v send to node %s\n", duration, r.NodeID)
-		fmt.Printf("Resource: /reservations/%v\n", id)
+	id, err := client.ReserveJSX(jsx)
+	if err != nil {
+		return errors.Wrap(err, "failed to send reservation")
 	}
 
+	fmt.Printf("Reservation for %v send to node bcdb\n", duration)
+	fmt.Printf("Resource: /reservations/%v\n", id)
+
 	return nil
 }
 
-func embed(schema interface{}, t provision.ReservationType) (*provision.Reservation, error) {
+func embed(schema interface{}, t provision.ReservationType, node string) (*provision.Reservation, error) {
 	raw, err := json.Marshal(schema)
 	if err != nil {
 		return nil, err
 	}
-
 	r := &provision.Reservation{
-		Type: t,
-		Data: raw,
+		NodeID: node,
+		Type:   t,
+		Data:   raw,
 	}
 
 	return r, nil
@@ -216,7 +218,7 @@ func cmdsDeleteReservation(c *cli.Context) error {
 	if _, err := buf.WriteString(fmt.Sprint(resID)); err != nil {
 		return err
 	}
-	if _, err := buf.WriteString(reservation.JSON); err != nil {
+	if _, err := buf.Write(reservation.JSON); err != nil {
 		return err
 	}
 
diff --git a/tools/tfuser/cmds_volumes.go b/tools/tfuser/cmds_volumes.go
index 0c0c5c8e..916cace8 100644
--- a/tools/tfuser/cmds_volumes.go
+++ b/tools/tfuser/cmds_volumes.go
@@ -27,7 +27,7 @@ func generateVolume(c *cli.Context) error {
 		Type: provision.DiskType(t),
 	}
 
-	p, err := embed(v, provision.VolumeReservation)
+	p, err := embed(v, provision.VolumeReservation, c.String("node"))
 	if err != nil {
 		return err
 	}
diff --git a/tools/tfuser/cmds_zdb.go b/tools/tfuser/cmds_zdb.go
index dd31be60..3cca8dd7 100644
--- a/tools/tfuser/cmds_zdb.go
+++ b/tools/tfuser/cmds_zdb.go
@@ -38,7 +38,7 @@ func generateZDB(c *cli.Context) error {
 		Public:   public,
 	}
 
-	p, err := embed(zdb, provision.ZDBReservation)
+	p, err := embed(zdb, provision.ZDBReservation, c.String("node"))
 	if err != nil {
 		return err
 	}
diff --git a/tools/tfuser/main.go b/tools/tfuser/main.go
index aa43f9b1..765a0d71 100644
--- a/tools/tfuser/main.go
+++ b/tools/tfuser/main.go
@@ -125,12 +125,14 @@ func main() {
 							Usage: "Add a node to a existing network",
 							Flags: []cli.Flag{
 								cli.StringFlag{
-									Name:  "node",
-									Usage: "Node ID of the node to add to the network",
+									Name:     "node",
+									Usage:    "Node ID of the node to add to the network",
+									Required: true,
 								},
 								cli.StringFlag{
-									Name:  "subnet",
-									Usage: "Subnet to use on this node. The subnet needs to be included in the IP range of the network",
+									Name:     "subnet",
+									Usage:    "Subnet to use on this node. The subnet needs to be included in the IP range of the network",
+									Required: true,
 								},
 								cli.UintFlag{
 									Name:  "port",
@@ -148,8 +150,9 @@ func main() {
 							Usage: "Removes a Network Resource from the network",
 							Flags: []cli.Flag{
 								cli.StringFlag{
-									Name:  "node",
-									Usage: "Node ID to remove from the network",
+									Name:     "node",
+									Usage:    "Node ID to remove from the network",
+									Required: true,
 								},
 							},
 							Action: cmdsRemoveNode,
@@ -164,8 +167,9 @@ func main() {
 							Usage: "Add external access to the network",
 							Flags: []cli.Flag{
 								cli.StringFlag{
-									Name:  "node",
-									Usage: "Node ID of the node which will act as an access point",
+									Name:     "node",
+									Usage:    "Node ID of the node which will act as an access point",
+									Required: true,
 								},
 								cli.StringFlag{
 									Name:  "subnet",
@@ -189,8 +193,14 @@ func main() {
 					Usage: "Generate container provisioning schema",
 					Flags: []cli.Flag{
 						cli.StringFlag{
-							Name:  "flist",
-							Usage: "URL to the flist",
+							Name:     "node",
+							Usage:    "node id for the generated workload",
+							Required: true,
+						},
+						cli.StringFlag{
+							Name:     "flist",
+							Usage:    "URL to the flist",
+							Required: true,
 						},
 						cli.StringFlag{
 							Name:  "storage",
@@ -205,8 +215,9 @@ func main() {
 							Usage: "enable coreX",
 						},
 						cli.StringFlag{
-							Name:  "network",
-							Usage: "network ID the container needs to be part of",
+							Name:     "network",
+							Usage:    "network ID the container needs to be part of",
+							Required: true,
 						},
 						cli.StringSliceFlag{
 							Name:  "mounts",
@@ -217,8 +228,9 @@ func main() {
 							Usage: "environment variable to set into the container",
 						},
 						cli.StringFlag{
-							Name:  "ip",
-							Usage: "ip address to assign to the container",
+							Name:     "ip",
+							Usage:    "ip address to assign to the container",
+							Required: true,
 						},
 						cli.UintFlag{
 							Name:  "cpu",
@@ -243,14 +255,20 @@ func main() {
 							Name:    "volume",
 							Aliases: []string{"vol"},
 							Flags: []cli.Flag{
+								cli.StringFlag{
+									Name:     "node",
+									Usage:    "node id for the generated workload",
+									Required: true,
+								},
 								cli.Uint64Flag{
 									Name:  "size, s",
 									Usage: "Size of the volume in GiB",
 									Value: 1,
 								},
 								cli.StringFlag{
-									Name:  "type, t",
-									Usage: "Type of disk to use, HHD or SSD",
+									Name:     "type, t",
+									Usage:    "Type of disk to use, HHD or SSD",
+									Required: true,
 								},
 							},
 							Action: generateVolume,
@@ -259,18 +277,25 @@ func main() {
 							Name:  "zdb",
 							Usage: "reserve a 0-db namespace",
 							Flags: []cli.Flag{
+								cli.StringFlag{
+									Name:     "node",
+									Usage:    "node id for the generated workload",
+									Required: true,
+								},
 								cli.Uint64Flag{
 									Name:  "size, s",
 									Usage: "Size of the volume in GiB",
 									Value: 1,
 								},
 								cli.StringFlag{
-									Name:  "type, t",
-									Usage: "Type of disk to use, HHD or SSD",
+									Name:     "type, t",
+									Usage:    "Type of disk to use, HHD or SSD",
+									Required: true,
 								},
 								cli.StringFlag{
-									Name:  "mode, m",
-									Usage: "0-DB mode (user, seq)",
+									Name:     "mode, m",
+									Usage:    "0-DB mode (user, seq)",
+									Required: true,
 								},
 								cli.StringFlag{
 									Name:  "password, p",
@@ -307,8 +332,9 @@ func main() {
 							Usage: "Cluster token to set for kubernetes, this is encrypted by the nodes public key",
 						},
 						cli.StringFlag{
-							Name:  "node, n",
-							Usage: "node ID. Required if password is set to encrypt the password",
+							Name:     "node, n",
+							Usage:    "node ID.",
+							Required: true,
 						},
 						cli.StringSliceFlag{
 							Name:  "master-ips",
@@ -325,6 +351,11 @@ func main() {
 					Name:  "debug",
 					Usage: "Enable debug mode on a node. In this mode the forward its logs to the specified redis endpoint",
 					Flags: []cli.Flag{
+						cli.StringFlag{
+							Name:     "node",
+							Usage:    "node id for the generated workload",
+							Required: true,
+						},
 						cli.StringFlag{
 							Name: "endpoint",
 						},
@@ -346,10 +377,6 @@ func main() {
 					Usage: "path to the provisioning schema, use - to read from stdin",
 					Value: "provision.json",
 				},
-				cli.StringSliceFlag{
-					Name:  "node",
-					Usage: "Node ID where to deploy the workload",
-				},
 				cli.StringFlag{
 					Name:  "duration",
 					Usage: "duration of the reservation. By default is number of days. But also support notation with duration suffix like m for minute or h for hours",
@@ -366,6 +393,10 @@ func main() {
 					EnvVar:   "TF_USER_ID",
 					Required: true,
 				},
+				cli.BoolFlag{
+					Name:  "dry-run",
+					Usage: "dry run, prints the reservation instead of registering it",
+				},
 			},
 			Action: cmdsProvision,
 		},
@@ -397,10 +428,11 @@ func main() {
 			Name:  "live",
 			Usage: "show you all the reservations that are still alive",
 			Flags: []cli.Flag{
-				cli.StringFlag{
-					Name:   "seed",
-					Usage:  "path to the file container the seed of the user private key",
-					EnvVar: "SEED_PATH",
+				cli.Int64Flag{
+					Name:     "id",
+					Usage:    "user id associated with the seed",
+					EnvVar:   "TF_USER_ID",
+					Required: true,
 				},
 				cli.IntFlag{
 					Name:  "start",


Test patch:
diff --git a/pkg/gedis/commands_provision_test.go b/pkg/gedis/commands_provision_test.go
index 879dfa76..433366ea 100644
--- a/pkg/gedis/commands_provision_test.go
+++ b/pkg/gedis/commands_provision_test.go
@@ -132,7 +132,7 @@ func TestProvisionFeedback(t *testing.T) {
 		ID:        id,
 		Created:   time.Now(),
 		State:     provision.StateOk,
-		Data:      json.RawMessage("returned data"),
+		Data:      json.RawMessage("{}"),
 		Signature: "signature",
 	}
 
@@ -141,10 +141,10 @@ func TestProvisionFeedback(t *testing.T) {
 		"result": types.TfgridReservationResult1{
 			Category:   types.TfgridReservationResult1CategoryContainer,
 			WorkloadID: "101",
-			DataJSON:   string(result.Data),
+			DataJSON:   result.Data,
 			Signature:  result.Signature,
 			State:      types.TfgridReservationResult1StateOk,
-			Epoch:      schema.Date{result.Created},
+			Epoch:      schema.Date{Time: result.Created},
 		},
 	}