Repository: threefoldtech/zos
Pull request #540 (2020-02-19 11:38:55)

Report primitives

codecov[bot]: # [Codecov](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=h1) Report
> Merging [#540](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=desc) into [master](https://codecov.io/gh/threefoldtech/zos/commit/16844c1392b0291692905bfe845dfbb462a16e7c&el=desc) will **decrease** coverage by `0.63%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/threefoldtech/zos/pull/540/graphs/tree.svg?width=650&height=150&src=pr&token=f7Vmq1OAEm)](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #540      +/-   ##
==========================================
- Coverage   36.26%   35.62%   -0.64%     
==========================================
  Files          75       76       +1     
  Lines        5066     5170     +104     
==========================================
+ Hits         1837     1842       +5     
- Misses       2963     3062      +99     
  Partials      266      266              
```


| [Impacted Files](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/provision/resource\_units.go](https://codecov.io/gh/threefoldtech/zos/pull/540/diff?src=pr&el=tree#diff-cGtnL3Byb3Zpc2lvbi9yZXNvdXJjZV91bml0cy5nbw==) | `3.70% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=footer). Last update [16844c1...f34fed3](https://codecov.io/gh/threefoldtech/zos/pull/540?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go
index 0f082e53..ae85478c 100644
--- a/cmds/provisiond/main.go
+++ b/cmds/provisiond/main.go
@@ -105,7 +105,7 @@ func main() {
 		provision.NewDecommissionSource(localStore),
 	)
 
-	engine := provision.New(source, localStore, remoteStore)
+	engine := provision.New(nodeID.Identity(), source, localStore, remoteStore)
 	server.Register(zbus.ObjectID{Name: module, Version: "0.0.1"}, pkg.ProvisionMonitor(engine))
 
 	log.Info().
diff --git a/pkg/gedis/commands_provision.go b/pkg/gedis/commands_provision.go
index 8a068fbc..0c22f223 100644
--- a/pkg/gedis/commands_provision.go
+++ b/pkg/gedis/commands_provision.go
@@ -9,7 +9,8 @@ import (
 
 	"github.com/threefoldtech/zos/pkg/schema"
 
-	types "github.com/threefoldtech/zos/pkg/gedis/types/provision"
+	dtypes "github.com/threefoldtech/zos/pkg/gedis/types/directory"
+	ptypes "github.com/threefoldtech/zos/pkg/gedis/types/provision"
 	"github.com/threefoldtech/zos/pkg/provision"
 
 	"github.com/threefoldtech/zos/pkg"
@@ -28,8 +29,8 @@ var provisionOrder = map[provision.ReservationType]int{
 
 // Reserve provision.Reserver
 func (g *Gedis) Reserve(r *provision.Reservation) (string, error) {
-	res := types.TfgridReservation1{
-		DataReservation: types.TfgridReservationData1{},
+	res := ptypes.TfgridReservation1{
+		DataReservation: ptypes.TfgridReservationData1{},
 		// CustomerTid:     r.User, //TODO: wrong type.
 	}
 
@@ -40,23 +41,23 @@ func (g *Gedis) Reserve(r *provision.Reservation) (string, error) {
 
 	switch r.Type {
 	case provision.ContainerReservation:
-		res.DataReservation.Containers = []types.TfgridReservationContainer1{
+		res.DataReservation.Containers = []ptypes.TfgridReservationContainer1{
 			containerReservation(w, r.NodeID),
 		}
 	case provision.VolumeReservation:
-		res.DataReservation.Volumes = []types.TfgridReservationVolume1{
+		res.DataReservation.Volumes = []ptypes.TfgridReservationVolume1{
 			volumeReservation(w, r.NodeID),
 		}
 	case provision.ZDBReservation:
-		res.DataReservation.Zdbs = []types.TfgridReservationZdb1{
+		res.DataReservation.Zdbs = []ptypes.TfgridReservationZdb1{
 			zdbReservation(w, r.NodeID),
 		}
 	case provision.NetworkReservation:
-		res.DataReservation.Networks = []types.TfgridReservationNetwork1{
+		res.DataReservation.Networks = []ptypes.TfgridReservationNetwork1{
 			networkReservation(w),
 		}
 	case provision.KubernetesReservation:
-		res.DataReservation.Kubernetes = []types.TfgridWorkloadsReservationK8S1{
+		res.DataReservation.Kubernetes = []ptypes.TfgridWorkloadsReservationK8S1{
 			k8sReservation(w, r.NodeID),
 		}
 	}
@@ -69,7 +70,7 @@ func (g *Gedis) Reserve(r *provision.Reservation) (string, error) {
 		return "", err
 	}
 
-	res = types.TfgridReservation1{}
+	res = ptypes.TfgridReservation1{}
 	if err = json.Unmarshal(result, &res); err != nil {
 		return "", err
 	}
@@ -87,7 +88,7 @@ func (g *Gedis) Get(id string) (*provision.Reservation, error) {
 		return nil, err
 	}
 
-	var workload types.TfgridReservationWorkload1
+	var workload ptypes.TfgridReservationWorkload1
 
 	if err = json.Unmarshal(result, &workload); err != nil {
 		return nil, err
@@ -112,7 +113,7 @@ func (g *Gedis) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservati
 	}
 
 	var out struct {
-		Workloads []types.TfgridReservationWorkload1 `json:"workloads"`
+		Workloads []ptypes.TfgridReservationWorkload1 `json:"workloads"`
 	}
 
 	if err = json.Unmarshal(result, &out); err != nil {
@@ -140,27 +141,27 @@ func (g *Gedis) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservati
 // Feedback implements provision.Feedbacker
 func (g *Gedis) Feedback(id string, r *provision.Result) error {
 
-	var rType types.TfgridReservationResult1CategoryEnum
+	var rType ptypes.TfgridReservationResult1CategoryEnum
 	switch r.Type {
 	case provision.VolumeReservation:
-		rType = types.TfgridReservationResult1CategoryVolume
+		rType = ptypes.TfgridReservationResult1CategoryVolume
 	case provision.ContainerReservation:
-		rType = types.TfgridReservationResult1CategoryContainer
+		rType = ptypes.TfgridReservationResult1CategoryContainer
 	case provision.ZDBReservation:
-		rType = types.TfgridReservationResult1CategoryZdb
+		rType = ptypes.TfgridReservationResult1CategoryZdb
 	case provision.NetworkReservation:
-		rType = types.TfgridReservationResult1CategoryNetwork
+		rType = ptypes.TfgridReservationResult1CategoryNetwork
 	}
 
-	var rState types.TfgridReservationResult1StateEnum
+	var rState ptypes.TfgridReservationResult1StateEnum
 	switch r.State {
 	case "ok":
-		rState = types.TfgridReservationResult1StateOk
+		rState = ptypes.TfgridReservationResult1StateOk
 	case "error":
-		rState = types.TfgridReservationResult1StateError
+		rState = ptypes.TfgridReservationResult1StateError
 	}
 
-	result := types.TfgridReservationResult1{
+	result := ptypes.TfgridReservationResult1{
 		Category:   rType,
 		WorkloadID: id,
 		DataJSON:   string(r.Data),
@@ -191,7 +192,22 @@ func (g *Gedis) Delete(id string) error {
 	return err
 }
 
-func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reservation, error) {
+// UpdateReservedResources send the amount of resource units reserved to BCDB
+func (g *Gedis) UpdateReservedResources(nodeID string, c provision.Counters) error {
+	r := dtypes.TfgridNodeResourceAmount1{
+		Cru: c.CRU.Current(),
+		Mru: c.MRU.Current(),
+		Hru: c.HRU.Current(),
+		Sru: c.SRU.Current(),
+	}
+	_, err := g.Send("tfgrid.directory.nodes", "update_reserved_capacity", Args{
+		"node_id":   nodeID,
+		"resources": r,
+	})
+	return err
+}
+
+func reservationFromSchema(w ptypes.TfgridReservationWorkload1) (*provision.Reservation, error) {
 	reservation := &provision.Reservation{
 		ID:        w.WorkloadID,
 		User:      w.User,
@@ -211,7 +227,7 @@ func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reser
 	// convert the workload description from jsx schema to zos types
 	switch reservation.Type {
 	case provision.ZDBReservation:
-		tmp := types.TfgridReservationZdb1{}
+		tmp := ptypes.TfgridReservationZdb1{}
 		if err := json.Unmarshal(reservation.Data, &tmp); err != nil {
 			return nil, err
 		}
@@ -222,7 +238,7 @@ func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reser
 		}
 
 	case provision.VolumeReservation:
-		tmp := types.TfgridReservationVolume1{}
+		tmp := ptypes.TfgridReservationVolume1{}
 		if err := json.Unmarshal(reservation.Data, &tmp); err != nil {
 			return nil, err
 		}
@@ -233,7 +249,7 @@ func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reser
 		}
 
 	case provision.NetworkReservation:
-		tmp := types.TfgridReservationNetwork1{}
+		tmp := ptypes.TfgridReservationNetwork1{}
 		if err := json.Unmarshal(reservation.Data, &tmp); err != nil {
 			return nil, err
 		}
@@ -244,7 +260,7 @@ func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reser
 		}
 
 	case provision.ContainerReservation:
-		tmp := types.TfgridReservationContainer1{}
+		tmp := ptypes.TfgridReservationContainer1{}
 		if err := json.Unmarshal(reservation.Data, &tmp); err != nil {
 			return nil, err
 		}
@@ -255,7 +271,7 @@ func reservationFromSchema(w types.TfgridReservationWorkload1) (*provision.Reser
 		}
 
 	case provision.KubernetesReservation:
-		tmp := types.TfgridWorkloadsReservationK8S1{}
+		tmp := ptypes.TfgridWorkloadsReservationK8S1{}
 		if err := json.Unmarshal(reservation.Data, &tmp); err != nil {
 			return nil, err
 		}
@@ -306,26 +322,26 @@ func workloadFromRaw(s json.RawMessage, t provision.ReservationType) (interface{
 	return nil, fmt.Errorf("unsupported reservation type %v", t)
 }
 
-func networkReservation(i interface{}) types.TfgridReservationNetwork1 {
+func networkReservation(i interface{}) ptypes.TfgridReservationNetwork1 {
 	n := i.(pkg.Network)
-	network := types.TfgridReservationNetwork1{
+	network := ptypes.TfgridReservationNetwork1{
 		Name:             n.Name,
 		Iprange:          n.IPRange.ToSchema(),
 		WorkloadID:       1,
-		NetworkResources: make([]types.TfgridNetworkNetResource1, len(n.NetResources)),
+		NetworkResources: make([]ptypes.TfgridNetworkNetResource1, len(n.NetResources)),
 	}
 
 	for i, nr := range n.NetResources {
-		network.NetworkResources[i] = types.TfgridNetworkNetResource1{
+		network.NetworkResources[i] = ptypes.TfgridNetworkNetResource1{
 			NodeID:                       nr.NodeID,
 			IPRange:                      nr.Subnet.ToSchema(),
 			WireguardPrivateKeyEncrypted: nr.WGPrivateKey,
 			WireguardPublicKey:           nr.WGPublicKey,
-			Peers:                        make([]types.WireguardPeer1, len(nr.Peers)),
+			Peers:                        make([]ptypes.WireguardPeer1, len(nr.Peers)),
 		}
 
 		for y, peer := range nr.Peers {
-			network.NetworkResources[i].Peers[y] = types.WireguardPeer1{
+			network.NetworkResources[i].Peers[y] = ptypes.WireguardPeer1{
 				Endpoint:   peer.Endpoint,
 				PublicKey:  peer.WGPublicKey,
 				AllowedIPs: make([]string, len(peer.AllowedIPs)),
@@ -339,17 +355,17 @@ func networkReservation(i interface{}) types.TfgridReservationNetwork1 {
 	return network
 }
 
-func containerReservation(i interface{}, nodeID string) types.TfgridReservationContainer1 {
+func containerReservation(i interface{}, nodeID string) ptypes.TfgridReservationContainer1 {
 	c := i.(provision.Container)
-	container := types.TfgridReservationContainer1{
+	container := ptypes.TfgridReservationContainer1{
 		// NodeID:      nodeID,
 		Flist:       c.FList,
 		HubURL:      c.FlistStorage,
 		Environment: c.Env,
 		Entrypoint:  c.Entrypoint,
 		Interactive: c.Interactive,
-		Volumes:     make([]types.TfgridReservationContainerMount1, len(c.Mounts)),
-		NetworkConnection: []types.TfgridReservationNetworkConnection1{
+		Volumes:     make([]ptypes.TfgridReservationContainerMount1, len(c.Mounts)),
+		NetworkConnection: []ptypes.TfgridReservationNetworkConnection1{
 			{
 				NetworkID: string(c.Network.NetworkID),
 				Ipaddress: c.Network.IPs[0],
@@ -361,7 +377,7 @@ func containerReservation(i interface{}, nodeID string) types.TfgridReservationC
 	}
 
 	for i, v := range c.Mounts {
-		container.Volumes[i] = types.TfgridReservationContainerMount1{
+		container.Volumes[i] = ptypes.TfgridReservationContainerMount1{
 			VolumeID:   v.VolumeID,
 			Mountpoint: v.Mountpoint,
 		}
@@ -369,10 +385,10 @@ func containerReservation(i interface{}, nodeID string) types.TfgridReservationC
 	return container
 }
 
-func volumeReservation(i interface{}, nodeID string) types.TfgridReservationVolume1 {
+func volumeReservation(i interface{}, nodeID string) ptypes.TfgridReservationVolume1 {
 	v := i.(provision.Volume)
 
-	volume := types.TfgridReservationVolume1{
+	volume := ptypes.TfgridReservationVolume1{
 		// WorkloadID:
 		// NodeID:
 		// ReservationID:
@@ -381,18 +397,18 @@ func volumeReservation(i interface{}, nodeID string) types.TfgridReservationVolu
 		// FarmerTid:
 	}
 	if v.Type == provision.HDDDiskType {
-		volume.Type = types.TfgridReservationVolume1TypeHDD
+		volume.Type = ptypes.TfgridReservationVolume1TypeHDD
 	} else if v.Type == provision.SSDDiskType {
-		volume.Type = types.TfgridReservationVolume1TypeSSD
+		volume.Type = ptypes.TfgridReservationVolume1TypeSSD
 	}
 
 	return volume
 }
 
-func zdbReservation(i interface{}, nodeID string) types.TfgridReservationZdb1 {
+func zdbReservation(i interface{}, nodeID string) ptypes.TfgridReservationZdb1 {
 	z := i.(provision.ZDB)
 
-	zdb := types.TfgridReservationZdb1{
+	zdb := ptypes.TfgridReservationZdb1{
 		// WorkloadID:
 		// NodeID:
 		// ReservationID:
@@ -403,24 +419,24 @@ func zdbReservation(i interface{}, nodeID string) types.TfgridReservationZdb1 {
 		// FarmerTid:
 	}
 	if z.DiskType == pkg.SSDDevice {
-		zdb.DiskType = types.TfgridReservationZdb1DiskTypeHdd
+		zdb.DiskType = ptypes.TfgridReservationZdb1DiskTypeHdd
 	} else if z.DiskType == pkg.HDDDevice {
-		zdb.DiskType = types.TfgridReservationZdb1DiskTypeSsd
+		zdb.DiskType = ptypes.TfgridReservationZdb1DiskTypeSsd
 	}
 
 	if z.Mode == pkg.ZDBModeUser {
-		zdb.Mode = types.TfgridReservationZdb1ModeUser
+		zdb.Mode = ptypes.TfgridReservationZdb1ModeUser
 	} else if z.Mode == pkg.ZDBModeSeq {
-		zdb.Mode = types.TfgridReservationZdb1ModeSeq
+		zdb.Mode = ptypes.TfgridReservationZdb1ModeSeq
 	}
 
 	return zdb
 }
 
-func k8sReservation(i interface{}, nodeID string) types.TfgridWorkloadsReservationK8S1 {
+func k8sReservation(i interface{}, nodeID string) ptypes.TfgridWorkloadsReservationK8S1 {
 	k := i.(provision.Kubernetes)
 
-	k8s := types.TfgridWorkloadsReservationK8S1{
+	k8s := ptypes.TfgridWorkloadsReservationK8S1{
 		// WorkloadID      int64
 		NodeID:        nodeID,
 		Size:          k.Size,
diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go
index 5dd7e34d..06f56f10 100644
--- a/pkg/provision/engine.go
+++ b/pkg/provision/engine.go
@@ -20,7 +20,7 @@ type ReservationCache interface {
 	Get(id string) (*Reservation, error)
 	Remove(id string) error
 	Exists(id string) (bool, error)
-	Counters() pkg.ProvisionCounters
+	Counters() Counters
 }
 
 // Feedbacker defines the method that needs to be implemented
@@ -28,9 +28,11 @@ type ReservationCache interface {
 type Feedbacker interface {
 	Feedback(id string, r *Result) error
 	Deleted(id string) error
+	UpdateReservedResources(nodeID string, c Counters) error
 }
 
 type defaultEngine struct {
+	nodeID string
 	source ReservationSource
 	store  ReservationCache
 	fb     Feedbacker
@@ -42,8 +44,9 @@ type defaultEngine struct {
 // the default implementation is a single threaded worker. so it process
 // one reservation at a time. On error, the engine will log the error. and
 // continue to next reservation.
-func New(source ReservationSource, rw ReservationCache, fb Feedbacker) Engine {
+func New(nodeID string, source ReservationSource, rw ReservationCache, fb Feedbacker) Engine {
 	return &defaultEngine{
+		nodeID: nodeID,
 		source: source,
 		store:  rw,
 		fb:     fb,
@@ -91,6 +94,9 @@ func (e *defaultEngine) Run(ctx context.Context) error {
 					continue
 				}
 			}
+			if err := e.fb.UpdateReservedResources(e.nodeID, e.store.Counters()); err != nil {
+				log.Error().Err(err).Msg("failed to updated the used resources")
+			}
 		}
 	}
 }
@@ -217,9 +223,18 @@ func (e *defaultEngine) Counters(ctx context.Context) <-chan pkg.ProvisionCounte
 			case <-ctx.Done():
 			}
 
+			c := e.store.Counters()
+			pc := pkg.ProvisionCounters{
+				Container: int64(c.containers),
+				Network:   int64(c.networks),
+				ZDB:       int64(c.zdbs),
+				Volume:    int64(c.volumes),
+				VM:        int64(c.vms),
+			}
+
 			select {
 			case <-ctx.Done():
-			case ch <- e.store.Counters():
+			case ch <- pc:
 			}
 		}
 	}()
diff --git a/pkg/provision/local_store.go b/pkg/provision/local_store.go
index 388cac71..8cbacc82 100644
--- a/pkg/provision/local_store.go
+++ b/pkg/provision/local_store.go
@@ -11,28 +11,27 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
-	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/app"
 	"github.com/threefoldtech/zos/pkg/versioned"
 )
 
 // Counter interface
 type Counter interface {
-	// Increment counter atomically by one
-	Increment() int64
-	// Decrement counter atomically by one
-	Decrement() int64
+	// Increment counter atomically by v
+	Increment(v int64) int64
+	// Decrement counter atomically by v
+	Decrement(v int64) int64
 	// Current returns the current value
 	Current() int64
 }
 
 type counterNop struct{}
 
-func (c *counterNop) Increment() int64 {
+func (c *counterNop) Increment(v int64) int64 {
 	return 0
 }
 
-func (c *counterNop) Decrement() int64 {
+func (c *counterNop) Decrement(v int64) int64 {
 	return 0
 }
 
@@ -44,13 +43,13 @@ func (c *counterNop) Current() int64 {
 type counterImpl int64
 
 // Increment counter atomically by one
-func (c *counterImpl) Increment() int64 {
-	return atomic.AddInt64((*int64)(c), 1)
+func (c *counterImpl) Increment(v int64) int64 {
+	return atomic.AddInt64((*int64)(c), v)
 }
 
 // Decrement counter atomically by one
-func (c *counterImpl) Decrement() int64 {
-	return atomic.AddInt64((*int64)(c), -1)
+func (c *counterImpl) Decrement(v int64) int64 {
+	return atomic.AddInt64((*int64)(c), -v)
 }
 
 // Current returns the current value
@@ -58,21 +57,31 @@ func (c *counterImpl) Current() int64 {
 	return atomic.LoadInt64((*int64)(c))
 }
 
-// FSStore is a in reservation store
-// using the filesystem as backend
-type FSStore struct {
-	sync.RWMutex
-	root string
+type (
+	// FSStore is a in reservation store
+	// using the filesystem as backend
+	FSStore struct {
+		sync.RWMutex
+		root     string
+		counters Counters
+	}
 
-	counters struct {
+	// Counters tracks the amount of primitives workload deployed and
+	// the amount of resource unit used
+	Counters struct {
 		containers counterImpl
 		volumes    counterImpl
 		networks   counterImpl
-		zdb        counterImpl
-		vm         counterImpl
-		debug      counterImpl
+		zdbs       counterImpl
+		vms        counterImpl
+		debugs     counterImpl
+
+		SRU counterImpl
+		HRU counterImpl
+		MRU counterImpl
+		CRU counterImpl
 	}
-}
+)
 
 // NewFSStore creates a in memory reservation store
 func NewFSStore(root string) (*FSStore, error) {
@@ -81,33 +90,54 @@ func NewFSStore(root string) (*FSStore, error) {
 		if err := os.RemoveAll(root); err != nil {
 			return nil, err
 		}
-		if err := os.MkdirAll(root, 0770); err != nil {
-			return nil, err
-		}
 
 		if err := app.MarkBooted("provisiond"); err != nil {
 			return nil, errors.Wrap(err, "fail to mark provisiond as booted")
 		}
+	}
 
-	} else {
-		log.Info().Msg("restart detected, keep reservation cache intact")
+	if err := os.MkdirAll(root, 0770); err != nil {
+		return nil, err
 	}
 
-	return &FSStore{
+	log.Info().Msg("restart detected, keep reservation cache intact")
+
+	store := &FSStore{
 		root: root,
-	}, nil
+	}
+
+	return store, store.sync()
 }
 
-// Counters returns stats about the cashed reservations
-func (s *FSStore) Counters() pkg.ProvisionCounters {
-	return pkg.ProvisionCounters{
-		Container: s.counters.containers.Current(),
-		Volume:    s.counters.volumes.Current(),
-		Network:   s.counters.networks.Current(),
-		ZDB:       s.counters.zdb.Current(),
-		VM:        s.counters.vm.Current(),
-		Debug:     s.counters.debug.Current(),
+func (s *FSStore) sync() error {
+	s.RLock()
+	defer s.RUnlock()
+
+	infos, err := ioutil.ReadDir(s.root)
+	if err != nil {
+		return err
 	}
+
+	for _, info := range infos {
+		if info.IsDir() {
+			continue
+		}
+
+		r, err := s.get(info.Name())
+		if err != nil {
+			return err
+		}
+
+		s.counterFor(r.Type).Increment(1)
+		s.processResourceUnits(r, true)
+	}
+
+	return nil
+}
+
+// Counters returns stats about the cashed reservations
+func (s *FSStore) Counters() Counters {
+	return s.counters
 }
 
 func (s *FSStore) counterFor(typ ReservationType) Counter {
@@ -119,11 +149,11 @@ func (s *FSStore) counterFor(typ ReservationType) Counter {
 	case NetworkReservation:
 		return &s.counters.networks
 	case ZDBReservation:
-		return &s.counters.zdb
+		return &s.counters.zdbs
 	case DebugReservation:
-		return &s.counters.debug
+		return &s.counters.debugs
 	case KubernetesReservation:
-		return &s.counters.vm
+		return &s.counters.vms
 	default:
 		// this will avoid nil pointer
 		return &counterNop{}
@@ -157,7 +187,10 @@ func (s *FSStore) Add(r *Reservation) error {
 		return err
 	}
 
-	s.counterFor(r.Type).Increment()
+	s.counterFor(r.Type).Increment(1)
+	if err := s.processResourceUnits(r, true); err != nil {
+		return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID)
+	}
 
 	return nil
 }
@@ -181,7 +214,10 @@ func (s *FSStore) Remove(id string) error {
 		return err
 	}
 
-	s.counterFor(r.Type).Decrement()
+	s.counterFor(r.Type).Decrement(1)
+	if err := s.processResourceUnits(r, false); err != nil {
+		return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID)
+	}
 
 	return nil
 }
@@ -203,7 +239,7 @@ func (s *FSStore) GetExpired() ([]*Reservation, error) {
 			continue
 		}
 
-		r, err := s.Get(info.Name())
+		r, err := s.get(info.Name())
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/provision/remote_store.go b/pkg/provision/remote_store.go
index 4d801519..82c13fed 100644
--- a/pkg/provision/remote_store.go
+++ b/pkg/provision/remote_store.go
@@ -188,3 +188,38 @@ func (s *HTTPStore) Delete(id string) error {
 	}
 	return nil
 }
+
+// UpdateReservedResources send the amount of resource units reserved to BCDB
+func (s *HTTPStore) UpdateReservedResources(nodeID string, c Counters) error {
+	url := fmt.Sprintf("%s/nodes/%s/used_resources", s.baseURL, nodeID)
+
+	u := resourceUnits{
+		CRU: int64(c.CRU),
+		MRU: int64(c.MRU),
+		SRU: int64(c.SRU),
+		HRU: int64(c.HRU),
+	}
+
+	buf := &bytes.Buffer{}
+	if err := json.NewEncoder(buf).Encode(u); err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("PUT", url, buf)
+	if err != nil {
+		return err
+	}
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return err
+	}
+
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("wrong response status code %s", resp.Status)
+	}
+	return nil
+
+}
diff --git a/pkg/provision/resource_units.go b/pkg/provision/resource_units.go
new file mode 100644
index 00000000..98066cc2
--- /dev/null
+++ b/pkg/provision/resource_units.go
@@ -0,0 +1,119 @@
+package provision
+
+import (
+	"encoding/json"
+	"fmt"
+	"math"
+)
+
+func (s *FSStore) processResourceUnits(r *Reservation, addOrRemoveBool bool) error {
+
+	var (
+		u   resourceUnits
+		err error
+	)
+
+	switch r.Type {
+	case VolumeReservation:
+		u, err = processVolume(r)
+	case ContainerReservation:
+		u, err = processContainer(r)
+	case ZDBReservation:
+		u, err = processZdb(r)
+	case KubernetesReservation:
+		u, err = processKubernetes(r)
+	}
+	if err != nil {
+		return err
+	}
+
+	if addOrRemoveBool {
+		s.counters.CRU.Increment(u.CRU)
+		s.counters.MRU.Increment(u.MRU)
+		s.counters.SRU.Increment(u.SRU)
+		s.counters.HRU.Increment(u.HRU)
+	} else {
+		s.counters.CRU.Decrement(u.CRU)
+		s.counters.MRU.Decrement(u.MRU)
+		s.counters.SRU.Decrement(u.SRU)
+		s.counters.HRU.Decrement(u.HRU)
+	}
+
+	return nil
+}
+
+type resourceUnits struct {
+	SRU int64 `json:"sru,omitempty"`
+	HRU int64 `json:"hru,omitempty"`
+	MRU int64 `json:"mru,omitempty"`
+	CRU int64 `json:"cru,omitempty"`
+}
+
+func processVolume(r *Reservation) (u resourceUnits, err error) {
+	var volume Volume
+	if err = json.Unmarshal(r.Data, &volume); err != nil {
+		return u, err
+	}
+
+	// volume.size and SRU is in GiB, not conversion needed
+	switch volume.Type {
+	case SSDDiskType:
+		u.SRU = int64(volume.Size)
+	case HDDDiskType:
+		u.HRU = int64(volume.Size)
+	}
+
+	return u, nil
+}
+
+func processContainer(r *Reservation) (u resourceUnits, err error) {
+	var cont Container
+	if err = json.Unmarshal(r.Data, &cont); err != nil {
+		return u, err
+	}
+	u.CRU = int64(cont.Capacity.CPU)
+	// memory is in MiB, but MRU is in GiB
+	u.MRU = int64(math.Ceil(float64(cont.Capacity.Memory) / 1024.0))
+
+	return u, nil
+}
+
+func processZdb(r *Reservation) (u resourceUnits, err error) {
+	if r.Type != ZDBReservation {
+		return u, fmt.Errorf("wrong type or reservation %s, excepted %s", r.Type, ZDBReservation)
+	}
+	var zdbVolume ZDB
+	if err := json.Unmarshal(r.Data, &zdbVolume); err != nil {
+		return u, err
+	}
+
+	switch zdbVolume.DiskType {
+	case "SSD":
+		u.SRU = int64(zdbVolume.Size)
+	case "HDD":
+		u.HRU = int64(zdbVolume.Size)
+	}
+
+	return u, nil
+}
+
+func processKubernetes(r *Reservation) (u resourceUnits, err error) {
+	var k8s Kubernetes
+	if err = json.Unmarshal(r.Data, &k8s); err != nil {
+		return u, err
+	}
+
+	// size are defined at https://github.com/threefoldtech/zos/blob/master/pkg/provision/kubernetes.go#L311
+	switch k8s.Size {
+	case 1:
+		u.CRU = 1
+		u.MRU = 2
+		u.SRU = 50
+	case 2:
+		u.CRU = 2
+		u.MRU = 4
+		u.SRU = 100
+	}
+
+	return u, nil
+}
diff --git a/tools/bcdb_mock/main.go b/tools/bcdb_mock/main.go
index ae1dcc5f..84b8c87c 100644
--- a/tools/bcdb_mock/main.go
+++ b/tools/bcdb_mock/main.go
@@ -56,6 +56,7 @@ func main() {
 	router.HandleFunc("/nodes/{node_id}/capacity", nodeStore.registerCapacity).Methods("POST")
 	router.HandleFunc("/nodes/{node_id}/uptime", nodeStore.updateUptimeHandler).Methods("POST")
 	router.HandleFunc("/nodes", nodeStore.listNodes).Methods("GET")
+	router.HandleFunc("/nodes/{node_id}/used_resources", nodeStore.updateUsedResources).Methods("PUT")
 
 	router.HandleFunc("/farms", farmStore.registerFarm).Methods("POST")
 	router.HandleFunc("/farms", farmStore.listFarm).Methods("GET")
diff --git a/tools/bcdb_mock/node_handlers.go b/tools/bcdb_mock/node_handlers.go
index a54a1601..7634f1f4 100644
--- a/tools/bcdb_mock/node_handlers.go
+++ b/tools/bcdb_mock/node_handlers.go
@@ -252,3 +252,33 @@ func (s *nodeStore) updateUptimeHandler(w http.ResponseWriter, r *http.Request)
 	}
 	w.WriteHeader(http.StatusOK)
 }
+
+func (s *nodeStore) updateUsedResources(w http.ResponseWriter, r *http.Request) {
+	defer r.Body.Close()
+
+	u := struct {
+		SRU int64 `json:"sru,omitempty"`
+		HRU int64 `json:"hru,omitempty"`
+		MRU int64 `json:"mru,omitempty"`
+		CRU int64 `json:"cru,omitempty"`
+	}{}
+	if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
+		httpError(w, err, http.StatusBadRequest)
+		return
+	}
+
+	nodeID := mux.Vars(r)["node_id"]
+
+	usedRescources := directory.TfgridNodeResourceAmount1{
+		Cru: int64(u.CRU),
+		Sru: int64(u.SRU),
+		Hru: int64(u.HRU),
+		Mru: int64(u.MRU),
+	}
+
+	if err := s.updateReservedCapacity(nodeID, usedRescources); err != nil {
+		httpError(w, err, http.StatusNotFound)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+}
diff --git a/tools/bcdb_mock/nodes_store.go b/tools/bcdb_mock/nodes_store.go
index 13eb1428..cac7a35e 100644
--- a/tools/bcdb_mock/nodes_store.go
+++ b/tools/bcdb_mock/nodes_store.go
@@ -102,6 +102,10 @@ func (s *nodeStore) updateTotalCapacity(nodeID string, cap directory.TfgridNodeR
 	return s.updateCapacity(nodeID, "total", cap)
 }
 
+func (s *nodeStore) updateReservedCapacity(nodeID string, cap directory.TfgridNodeResourceAmount1) error {
+	return s.updateCapacity(nodeID, "reserved", cap)
+}
+
 func (s *nodeStore) updateCapacity(nodeID string, t string, cap directory.TfgridNodeResourceAmount1) error {
 	node, err := s.Get(nodeID)
 	if err != nil {


Test patch:
diff --git a/pkg/provision/resource_units_test.go b/pkg/provision/resource_units_test.go
new file mode 100644
index 00000000..b7071bb4
--- /dev/null
+++ b/pkg/provision/resource_units_test.go
@@ -0,0 +1,243 @@
+package provision
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/threefoldtech/zos/pkg"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_processZDB(t *testing.T) {
+	type args struct {
+		r *Reservation
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		want    resourceUnits
+		wantErr bool
+	}{
+		{
+			name: "zdbSSD",
+			args: args{
+				r: &Reservation{
+					Type: ZDBReservation,
+					Data: mustMarshalJSON(t, ZDB{
+						Size:     1,
+						DiskType: pkg.SSDDevice,
+					}),
+				},
+			},
+			want: resourceUnits{
+				SRU: 1,
+			},
+			wantErr: false,
+		},
+		{
+			name: "zdbHDD",
+			args: args{
+				r: &Reservation{
+					Type: ZDBReservation,
+					Data: mustMarshalJSON(t, ZDB{
+						Size:     1,
+						DiskType: pkg.HDDDevice,
+					}),
+				},
+			},
+			want: resourceUnits{
+				HRU: 1,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := processZdb(tt.args.r)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Equal(t, tt.want, got)
+			}
+		})
+	}
+}
+
+func Test_processVolume(t *testing.T) {
+	type args struct {
+		r *Reservation
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		wantU   resourceUnits
+		wantErr bool
+	}{
+		{
+			name: "volumeSSD",
+			args: args{
+				r: &Reservation{
+					Type: VolumeReservation,
+					Data: mustMarshalJSON(t, Volume{
+						Size: 1,
+						Type: SSDDiskType,
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				SRU: 1,
+			},
+		},
+		{
+			name: "volumeHDD",
+			args: args{
+				r: &Reservation{
+					Type: VolumeReservation,
+					Data: mustMarshalJSON(t, Volume{
+						Size: 1,
+						Type: HDDDiskType,
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				HRU: 1,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotU, err := processVolume(tt.args.r)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Equal(t, tt.wantU, gotU)
+			}
+		})
+	}
+}
+
+func Test_processContainer(t *testing.T) {
+	type args struct {
+		r *Reservation
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		wantU   resourceUnits
+		wantErr bool
+	}{
+		{
+			name: "container",
+			args: args{
+				r: &Reservation{
+					Type: VolumeReservation,
+					Data: mustMarshalJSON(t, Container{
+						Capacity: ContainerCapacity{
+							CPU:    2,
+							Memory: 1024,
+						},
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				CRU: 2,
+				MRU: 1,
+			},
+		},
+		{
+			name: "container",
+			args: args{
+				r: &Reservation{
+					Type: VolumeReservation,
+					Data: mustMarshalJSON(t, Container{
+						Capacity: ContainerCapacity{
+							CPU:    2,
+							Memory: 2000,
+						},
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				CRU: 2,
+				MRU: 2,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotU, err := processContainer(tt.args.r)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Equal(t, tt.wantU, gotU)
+			}
+		})
+	}
+}
+
+func Test_processKubernetes(t *testing.T) {
+	type args struct {
+		r *Reservation
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		wantU   resourceUnits
+		wantErr bool
+	}{
+		{
+			name: "k8sSize1",
+			args: args{
+				r: &Reservation{
+					Type: KubernetesReservation,
+					Data: mustMarshalJSON(t, Kubernetes{
+						Size: 1,
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				CRU: 1,
+				MRU: 2,
+				SRU: 50,
+			},
+		},
+		{
+			name: "k8sSize2",
+			args: args{
+				r: &Reservation{
+					Type: KubernetesReservation,
+					Data: mustMarshalJSON(t, Kubernetes{
+						Size: 2,
+					}),
+				},
+			},
+			wantU: resourceUnits{
+				CRU: 2,
+				MRU: 4,
+				SRU: 100,
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotU, err := processKubernetes(tt.args.r)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Equal(t, tt.wantU, gotU)
+			}
+		})
+	}
+}
+
+func mustMarshalJSON(t *testing.T, v interface{}) []byte {
+	b, err := json.Marshal(v)
+	require.NoError(t, err)
+	return b
+}