Repository: threefoldtech/zos
Pull request #1711 (2022-05-24 09:14:12)

Support pausing workloads
This PR is mainly a refactor to clean up how provisioners are implemented. This is most of the changes are dedicated to. It also allow provisioners to set specific status on workloads by returning a proper Response() as an error. 

This was done in a backward compatible way by still allowing to return a normal error on failure. So a provision action can still return err = nil, or err = some error, or a custom Response (using one of the available Response methods) to force some other status than ok, error.

This allowed us to easily implement Pause/Resume actions on workloads. By default any workload that has no implementation for those actions will still be marked as paused/resumed. 

Workload like VMs, and ZDBs will actually take a pause/resume action to suspend operation on the workload.


OmarElawady: Some places where StateOk may need to be replaced by IsOkay:
- https://github.com/threefoldtech/zos/blob/9f450c7448c4531d0678322bd09277af047c9af4/pkg/provision/storage/storage.go#L671 as the paused vms for example still has its resources allocated
- It's not very clear how the pause/resume would be implemented on the tfchain side, and thus whether it's possible to have ok workloads and paused workloads on the same deployment. But if this is the case, this can cause a problem for paused public ips getting ignored [here](https://github.com/threefoldtech/zos/blob/9f450c7448c4531d0678322bd09277af047c9af4/pkg/primitives/pubip/public_ip.go#L91) which can lead to conflict in cases like (make a deployment with 1 pub ip -> the pub ip gets paused -> a new pubip workload is added, it would get the one assigned to the paused workload -> the old pub ip revives).
- https://github.com/threefoldtech/zos/blob/9f450c7448c4531d0678322bd09277af047c9af4/pkg/provision/mbus/network.go#L146 not sure who depends on this. But paused Public IPs can't be reserved by other contracts, right? So it's considered still reserved by the hosting node even if it's paused?

Patch:
diff --git a/cmds/modules/provisiond/cap.go b/cmds/modules/provisiond/cap.go
index 54c384c2..b9e69293 100644
--- a/cmds/modules/provisiond/cap.go
+++ b/cmds/modules/provisiond/cap.go
@@ -51,7 +51,7 @@ func (c *CapacitySetter) setWithClient(cl *substrate.Substrate, deployments ...g
 		var total gridtypes.Capacity
 		for i := range deployment.Workloads {
 			wl := &deployment.Workloads[i]
-			if wl.Result.State.IsAny(gridtypes.StateOk) {
+			if wl.Result.State.IsOkay() {
 				cap, err := wl.Capacity()
 				if err != nil {
 					log.Error().Err(err).Str("workload", wl.Name.String()).
diff --git a/cmds/modules/provisiond/events.go b/cmds/modules/provisiond/events.go
index 36af3015..7da5e34c 100644
--- a/cmds/modules/provisiond/events.go
+++ b/cmds/modules/provisiond/events.go
@@ -95,11 +95,16 @@ func (r *ContractEventHandler) Run(ctx context.Context) error {
 	// take into account the following:
 	// every is in seconds.
 	events := stubs.NewEventsStub(r.cl)
-	stream, err := events.ContractCancelledEvent(ctx)
+	cancellation, err := events.ContractCancelledEvent(ctx)
 	if err != nil {
 		return errors.Wrap(err, "failed to register to node events")
 	}
 
+	locking, err := events.ContractLockedEvent(ctx)
+	if err != nil {
+		return err
+	}
+
 	if err := r.sync(ctx); err != nil {
 		return errors.Wrap(err, "failed to synchronize active contracts")
 	}
@@ -117,7 +122,7 @@ func (r *ContractEventHandler) Run(ctx context.Context) error {
 					log.Error().Err(err).Msg("failed to synchronize contracts with the chain")
 				}
 			}()
-		case event := <-stream:
+		case event := <-cancellation:
 			if event.Kind == pkg.EventSubscribed {
 				// we run this in a go routine because we don't
 				// want synchronization of contracts on the chain (that can take some time)
@@ -139,6 +144,21 @@ func (r *ContractEventHandler) Run(ctx context.Context) error {
 					Uint64("contract", event.Contract).
 					Msg("failed to decomission contract")
 			}
+		case event := <-locking:
+			// todo. we might need to try to sync all contracts to real state if we
+			// missed events. so another way to sync here.
+			action := r.engine.Resume
+			if event.Lock {
+				action = r.engine.Pause
+			}
+
+			if err := action(ctx, event.TwinId, event.Contract); err != nil {
+				log.Error().Err(err).
+					Uint32("twin", event.TwinId).
+					Uint64("contract", event.Contract).
+					Bool("lock", event.Lock).
+					Msg("failed to set deployment locking contract")
+			}
 		}
 	}
 }
diff --git a/cmds/modules/provisiond/main.go b/cmds/modules/provisiond/main.go
index 6a73419d..3e5150ef 100644
--- a/cmds/modules/provisiond/main.go
+++ b/cmds/modules/provisiond/main.go
@@ -330,8 +330,8 @@ func action(cli *cli.Context) error {
 		Msg("starting provision module")
 
 	// call the runtime upgrade before running engine
-	if err := provisioners.InitializeZDB(ctx); err != nil {
-		log.Error().Err(err).Msg("failed to initialize zdb subsystem")
+	if err := provisioners.Initialize(ctx); err != nil {
+		log.Error().Err(err).Msg("failed to run provisioners initializers")
 	}
 
 	// spawn the engine
diff --git a/go.mod b/go.mod
index 2bd83eb7..0ea8620b 100644
--- a/go.mod
+++ b/go.mod
@@ -22,6 +22,7 @@ require (
 	github.com/containernetworking/plugins v0.9.1
 	github.com/coreos/go-iptables v0.6.0 // indirect
 	github.com/deckarep/golang-set v1.8.0
+	github.com/decred/base58 v1.0.3 // indirect
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/diskfs/go-diskfs v1.2.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.1
@@ -63,7 +64,7 @@ require (
 	github.com/stretchr/testify v1.7.0
 	github.com/threefoldtech/0-fs v1.3.1-0.20201203163303-d963de9adea7
 	github.com/threefoldtech/go-rmb v0.1.11-0.20220224131627-825c23c921d3
-	github.com/threefoldtech/substrate-client v0.0.0-20220513092006-992cef6e4dc1
+	github.com/threefoldtech/substrate-client v0.0.0-20220527082351-144dbbd71bfe
 	github.com/threefoldtech/zbus v0.1.5
 	github.com/tinylib/msgp v1.1.5 // indirect
 	github.com/tyler-smith/go-bip39 v1.1.0
diff --git a/go.sum b/go.sum
index 5949e476..80fe4fdc 100644
--- a/go.sum
+++ b/go.sum
@@ -1098,6 +1098,10 @@ github.com/threefoldtech/substrate-client v0.0.0-20220408115405-27cf5b8db815 h1:
 github.com/threefoldtech/substrate-client v0.0.0-20220408115405-27cf5b8db815/go.mod h1:Lpsz7gVNSdbWD8erfVh1AZv2wnProc7g/QHJiYp5lP8=
 github.com/threefoldtech/substrate-client v0.0.0-20220513092006-992cef6e4dc1 h1:DcGZVte73q4xkZoNFe3aEKl6d1ESw+/EBAiucs/r9/M=
 github.com/threefoldtech/substrate-client v0.0.0-20220513092006-992cef6e4dc1/go.mod h1:Lpsz7gVNSdbWD8erfVh1AZv2wnProc7g/QHJiYp5lP8=
+github.com/threefoldtech/substrate-client v0.0.0-20220524110635-423017f8264e h1:Gjxq/MnOAv9pgOuzCASX9jdD6AvdY5+hvkjULDgYvVA=
+github.com/threefoldtech/substrate-client v0.0.0-20220524110635-423017f8264e/go.mod h1:Lpsz7gVNSdbWD8erfVh1AZv2wnProc7g/QHJiYp5lP8=
+github.com/threefoldtech/substrate-client v0.0.0-20220527082351-144dbbd71bfe h1:D4PHbq6XFVW1qVdV4ucVJ4L+NU4mg0k54omf3mSK/n8=
+github.com/threefoldtech/substrate-client v0.0.0-20220527082351-144dbbd71bfe/go.mod h1:Lpsz7gVNSdbWD8erfVh1AZv2wnProc7g/QHJiYp5lP8=
 github.com/threefoldtech/zbus v0.1.5 h1:S9kXbjejoRRnJw1yKHEXFGF2vqL+Drae2n4vpj0pGHo=
 github.com/threefoldtech/zbus v0.1.5/go.mod h1:ZtiRpcqzEBJetVQDsEbw0p48h/AF3O1kf0tvd30I0BU=
 github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
diff --git a/pkg/events.go b/pkg/events.go
index f2c9c846..d4184ce5 100644
--- a/pkg/events.go
+++ b/pkg/events.go
@@ -38,7 +38,18 @@ type ContractCancelledEvent struct {
 	TwinId   uint32
 }
 
+// ContractLockedEvent is raised when a contract is locked/unlocked. On locking the Lock flag will be set to true.
+// If Kind is EventSubscribed it means event stream has been reconnected and might be events loss. It's up to the
+// handler of this event type to make sure contracts are synched with the grid.
+type ContractLockedEvent struct {
+	Kind     EventKind
+	Contract uint64
+	TwinId   uint32
+	Lock     bool
+}
+
 type Events interface {
 	PublicConfigEvent(ctx context.Context) <-chan PublicConfigEvent
 	ContractCancelledEvent(ctx context.Context) <-chan ContractCancelledEvent
+	ContractLockedEvent(ctx context.Context) <-chan ContractLockedEvent
 }
diff --git a/pkg/events/events.go b/pkg/events/events.go
index bf5cefc9..b031d70c 100644
--- a/pkg/events/events.go
+++ b/pkg/events/events.go
@@ -18,20 +18,22 @@ var (
 )
 
 type Manager struct {
-	mgr           substrate.Manager
-	node          uint32
-	pubCfg        chan pkg.PublicConfigEvent
-	contactCancel chan pkg.ContractCancelledEvent
+	mgr            substrate.Manager
+	node           uint32
+	pubCfg         chan pkg.PublicConfigEvent
+	contractCancel chan pkg.ContractCancelledEvent
+	contractLocked chan pkg.ContractLockedEvent
 
 	o sync.Once
 }
 
 func New(mgr substrate.Manager, node uint32) pkg.Events {
 	return &Manager{
-		mgr:           mgr,
-		node:          node,
-		pubCfg:        make(chan pkg.PublicConfigEvent),
-		contactCancel: make(chan pkg.ContractCancelledEvent),
+		mgr:            mgr,
+		node:           node,
+		pubCfg:         make(chan pkg.PublicConfigEvent),
+		contractCancel: make(chan pkg.ContractCancelledEvent),
+		contractLocked: make(chan pkg.ContractLockedEvent),
 	}
 }
 
@@ -54,7 +56,7 @@ func (m *Manager) events(ctx context.Context) error {
 	defer subClient.Client.Close()
 
 	m.pubCfg <- pkg.PublicConfigEvent{Kind: pkg.EventSubscribed}
-	m.contactCancel <- pkg.ContractCancelledEvent{Kind: pkg.EventSubscribed}
+	m.contractCancel <- pkg.ContractCancelledEvent{Kind: pkg.EventSubscribed}
 
 	// Subscribe to system events via storage
 	key, err := types.CreateStorageKey(meta, "System", "Events", nil)
@@ -98,12 +100,38 @@ func (m *Manager) events(ctx context.Context) error {
 						continue
 					}
 					log.Info().Uint64("contract", uint64(e.ContractID)).Msg("got contract cancel update")
-					m.contactCancel <- pkg.ContractCancelledEvent{
+					m.contractCancel <- pkg.ContractCancelledEvent{
 						Kind:     pkg.EventReceived,
 						Contract: uint64(e.ContractID),
 						TwinId:   uint32(e.Twin),
 					}
 				}
+
+				for _, e := range events.SmartContractModule_ContractGracePeriodStarted {
+					if e.NodeID != types.U32(m.node) {
+						continue
+					}
+					log.Info().Uint64("contract", uint64(e.ContractID)).Msg("got contract grace period started")
+					m.contractLocked <- pkg.ContractLockedEvent{
+						Kind:     pkg.EventReceived,
+						Contract: uint64(e.ContractID),
+						TwinId:   uint32(e.TwinID),
+						Lock:     true,
+					}
+				}
+
+				for _, e := range events.SmartContractModule_ContractGracePeriodEnded {
+					if e.NodeID != types.U32(m.node) {
+						continue
+					}
+					log.Info().Uint64("contract", uint64(e.ContractID)).Msg("got contract grace period ended")
+					m.contractLocked <- pkg.ContractLockedEvent{
+						Kind:     pkg.EventReceived,
+						Contract: uint64(e.ContractID),
+						TwinId:   uint32(e.TwinID),
+						Lock:     false,
+					}
+				}
 			}
 		case err := <-reg.Err():
 			// need a reconnect
@@ -141,5 +169,13 @@ func (m *Manager) ContractCancelledEvent(ctx context.Context) <-chan pkg.Contrac
 		go m.start(ctx)
 	})
 
-	return m.contactCancel
+	return m.contractCancel
+}
+
+func (m *Manager) ContractLockedEvent(ctx context.Context) <-chan pkg.ContractLockedEvent {
+	m.o.Do(func() {
+		go m.start(ctx)
+	})
+
+	return m.contractLocked
 }
diff --git a/pkg/gridtypes/id.go b/pkg/gridtypes/id.go
index d56a7851..944deec1 100644
--- a/pkg/gridtypes/id.go
+++ b/pkg/gridtypes/id.go
@@ -1,9 +1,12 @@
 package gridtypes
 
 import (
+	"crypto/md5"
 	"fmt"
 	"regexp"
 	"strings"
+
+	"github.com/decred/base58"
 )
 
 var (
@@ -53,6 +56,24 @@ func (i WorkloadID) Parts() (twin uint32, deployment uint64, name Name, err erro
 	return
 }
 
+// Unique generate a unique predetermined short name based
+// on that ID and input value n.
+// returns a max of 13 char str which is suitable
+// to be used as devices and tap devices names.
+func (i WorkloadID) Unique(n string) string {
+	m := md5.New()
+
+	fmt.Fprintf(m, "%s:%s", i.String(), n)
+
+	h := m.Sum(nil)
+	b := base58.Encode(h[:])
+	if len(b) > 13 {
+		b = b[:13]
+	}
+
+	return string(b)
+}
+
 // IsValidName validates workload name
 func IsValidName(n Name) error {
 	if len(n) == 0 {
diff --git a/pkg/gridtypes/workload.go b/pkg/gridtypes/workload.go
index 0006e4ac..892ca3ef 100644
--- a/pkg/gridtypes/workload.go
+++ b/pkg/gridtypes/workload.go
@@ -232,6 +232,10 @@ func (s ResultState) IsAny(state ...ResultState) bool {
 	return false
 }
 
+func (s ResultState) IsOkay() bool {
+	return s.IsAny(StateOk, StatePaused)
+}
+
 const (
 	// StateInit is the first state of the workload on storage
 	StateInit ResultState = "init"
@@ -242,13 +246,15 @@ const (
 	StateError ResultState = "error"
 	// StateOk constant
 	StateOk ResultState = "ok"
-	//StateDeleted constant
+	// StateDeleted constant
 	StateDeleted ResultState = "deleted"
+	// StatePaused constant
+	StatePaused ResultState = "paused"
 )
 
 var (
 	validStates = []ResultState{
-		StateInit, StateUnChanged, StateError, StateOk, StateDeleted,
+		StateInit, StateUnChanged, StateError, StateOk, StateDeleted, StatePaused,
 	}
 )
 
diff --git a/pkg/primitives/cleanup.go b/pkg/primitives/cleanup.go
deleted file mode 100644
index b82d0b2b..00000000
--- a/pkg/primitives/cleanup.go
+++ /dev/null
@@ -1,307 +0,0 @@
-package primitives
-
-// var (
-// 	vdiskIDMatch = regexp.MustCompile(`^(\d+-\d+)`)
-// )
-
-// // janitorImpl structure
-// type janitorImpl struct {
-// 	zbus zbus.Client
-
-// 	getter provision.ReservationGetter
-// }
-
-// // NewJanitor creates a new Janitor instance
-// func NewJanitor(zbus zbus.Client, getter provision.ReservationGetter) provision.Janitor {
-// 	return &janitorImpl{
-// 		zbus:   zbus,
-// 		getter: getter,
-// 	}
-// }
-
-// // Cleanup cleans up unused resources
-// func (j *janitorImpl) Cleanup(ctx context.Context) error {
-// 	// - First remove all lingering zdb namespaces that has NO valid
-// 	// reservation. This will also decomission zdb containers that
-// 	// serves no namespaces anymore
-// 	if err := j.cleanupZdbContainers(ctx); err != nil {
-// 		log.Error().Err(err).Msg("zdb cleaner failed")
-// 		// we don't stop here. if we failed to clean zdb containers
-// 		// any lingering zdb container will end up in the protected
-// 		// volumes so there is no harm of continuing the process
-// 		// to clean what we can
-// 	}
-
-// 	// - Second, we clean up all lingering volumes on the node
-// 	if err := j.cleanupVolumes(ctx); err != nil {
-// 		log.Error().Err(err).Msg("volume cleaner failed")
-// 	}
-
-// 	// - Third, we clean up any lingering vdisks that are not being
-// 	// used.
-// 	if err := j.cleanupVdisks(ctx); err != nil {
-// 		log.Error().Err(err).Msg("virtual disks cleaner failed")
-// 	}
-
-// 	return nil
-// }
-
-// func (j *janitorImpl) checkToDelete(id string) (bool, error) {
-// 	log.Debug().Str("id", id).Msg("checking explorer for reservation")
-
-// 	reservation, err := j.getter.Get(id)
-// 	if err != nil {
-// 		var hErr client.HTTPError
-// 		if ok := errors.As(err, &hErr); ok {
-// 			resp := hErr.Response()
-// 			// If reservation is not found it should be deleted
-// 			if resp.StatusCode == 404 {
-// 				return true, nil
-// 			}
-// 		}
-
-// 		return false, err
-// 	}
-
-// 	return reservation.ToDelete, nil
-// }
-
-// func (j *janitorImpl) cleanupVdisks(ctx context.Context) error {
-// 	stub := stubs.NewVDiskModuleStub(j.zbus)
-
-// 	vdisks, err := stub.List()
-// 	if err != nil {
-// 		return errors.Wrap(err, "failed to list virtual disks")
-// 	}
-// 	for _, vdisk := range vdisks {
-// 		//fmt.Sscanf(str string, format string, a ...interface{})
-// 		gwid := vdiskIDMatch.FindString(vdisk.Name())
-// 		clog := log.With().Str("vdisk", vdisk.Name()).Str("id", gwid).Logger()
-// 		if len(gwid) == 0 {
-// 			clog.Warn().Msg("vdisk has invalid id, skipping")
-// 			continue
-// 		}
-
-// 		delete, err := j.checkToDelete(gwid)
-// 		if err != nil {
-// 			clog.Error().Err(err).Msg("failed to check vdisk reservation")
-// 			continue
-// 		}
-
-// 		if delete {
-// 			clog.Info().Str("reason", "no-associated-reservation").Msg("delete vdisk")
-// 			if err := stub.Deallocate(vdisk.Name()); err != nil {
-// 				clog.Error().Err(err).Msg("failed to deallocate vdisk")
-// 			}
-// 		} else {
-// 			clog.Info().Msg("skipping vdisk")
-// 		}
-// 	}
-
-// 	return nil
-// }
-
-// func (j *janitorImpl) cleanupVolumes(ctx context.Context) error {
-// 	storaged := stubs.NewStorageModuleStub(j.zbus)
-// 	// We get a list with ALL volumes, that are being
-// 	// used by active containers. Note we don't check if
-// 	// containers are valid or not. This code is only for
-// 	// storage cleanup (so far)
-// 	protected, err := j.protectedVolumesFromContainers(ctx)
-// 	if err != nil {
-// 		return errors.Wrap(err, "failed to list retrieve protected volumes")
-// 	}
-
-// 	// - The we list all volumes from storage.
-// 	// we need to go all each one and do the following checks
-// 	//  - Are they protected ?
-// 	//  - Do they belong to active reservation ?
-// 	//  - If not, delete!
-// 	volumes, err := storaged.ListFilesystems()
-// 	if err != nil {
-// 		return err
-// 	}
-
-// 	for _, volume := range volumes {
-// 		clog := log.With().Str("volume", volume.Path).Logger()
-
-// 		clog.Debug().Msg("checking volume for clean up")
-
-// 		// - Is the volume protected
-// 		if _, ok := protected[volume.Path]; ok {
-// 			clog.Debug().Msg("volume is protected, skipping")
-// 			continue
-// 		}
-
-// 		if len(volume.Name) == 64 {
-// 			// if the fs is not used by any container and its name is 64 character long
-// 			// they are left over of old containers when flistd used to generate random names
-// 			// for the container root flist subvolumes
-// 			clog.Info().Str("reason", "legacy-root-fs").Msg("delete subvolume")
-// 			if err := storaged.ReleaseFilesystem(volume.Name); err != nil {
-// 				clog.Error().Err(err).Msg("failed to delete subvol")
-// 			}
-
-// 			continue
-// 		}
-
-// 		if strings.HasPrefix(volume.Name, storage.ZDBPoolPrefix) {
-// 			clog.Info().Str("reason", "unused-zdb").Msg("delete subvolume")
-// 			if err := storaged.ReleaseFilesystem(volume.Name); err != nil {
-// 				clog.Error().Err(err).Msg("failed to delete subvol")
-// 			}
-
-// 			continue
-// 		}
-
-// 		if volume.Name == "fcvms" {
-// 			// left over from testing during vm module development
-// 			clog.Info().Str("reason", "legacy-vm-fs").Msg("delete subvolume")
-// 			if err := storaged.ReleaseFilesystem(volume.Name); err != nil {
-// 				clog.Error().Err(err).Msg("failed to delete subvol")
-// 			}
-
-// 			continue
-// 		}
-
-// 		// So this is NOT protected, and obviously
-// 		// not matching any of the above criteria
-// 		// so we need to check if we can delete this reservation
-// 		// Check the explorer if it needs to be deleted
-// 		delete, err := j.checkToDelete(volume.Name)
-// 		if err != nil {
-// 			//TODO: handle error here
-// 			clog.Error().Err(err).Msg("failed to check volume reservation")
-// 			continue
-// 		}
-
-// 		if delete {
-// 			clog.Info().Str("reason", "no-associated-reservation").Msg("delete subvolume")
-// 			if err := storaged.ReleaseFilesystem(volume.Name); err != nil {
-// 				clog.Error().Err(err).Msg("failed to delete subvolume")
-// 			}
-// 		} else {
-// 			clog.Info().Msg("skipping subvolume")
-// 		}
-// 	}
-
-// 	return nil
-// }
-
-// func (j *janitorImpl) cleanupZdbContainer(ctx context.Context, id string) error {
-// 	con, err := newZdbConnection(id)
-// 	if err != nil {
-// 		return err
-// 	}
-
-// 	defer con.Close()
-// 	namespaces, err := con.Namespaces()
-// 	if err != nil {
-// 		// we need to skip this zdb container for now we are not sure
-// 		// if it has any used values.
-// 		return errors.Wrap(err, "failed to list zdb namespace")
-// 	}
-
-// 	mapped := make(map[string]struct{})
-// 	for _, namespace := range namespaces {
-// 		if namespace == "default" {
-// 			continue
-// 		}
-
-// 		mapped[namespace] = struct{}{}
-
-// 		toDelete, err := j.checkToDelete(namespace)
-// 		if err != nil {
-// 			log.Error().Err(err).Str("zdb-namespace", namespace).Msg("failed to check if we should keep namespace")
-// 			continue
-// 		}
-
-// 		if !toDelete {
-// 			continue
-// 		}
-
-// 		if err := con.DeleteNamespace(namespace); err != nil {
-// 			log.Error().Err(err).Str("zdb-namespace", namespace).Msg("failed to delete lingering zdb namespace")
-// 		}
-
-// 		delete(mapped, namespace)
-// 	}
-
-// 	if len(mapped) > 0 {
-// 		// not all namespaces are deleted so we need to keep this
-// 		// container instance
-// 		return nil
-// 	}
-
-// 	// no more namespace to keep, so container can also go
-// 	return common.DeleteZdbContainer(pkg.ContainerID(id), j.zbus)
-// }
-
-// func (j *janitorImpl) cleanupZdbContainers(ctx context.Context) error {
-// 	containerd := stubs.NewContainerModuleStub(j.zbus)
-
-// 	containers, err := containerd.List("zdb")
-// 	if err != nil {
-// 		return errors.Wrap(err, "failed to list zdb containers")
-// 	}
-
-// 	for _, containerID := range containers {
-// 		if err := j.cleanupZdbContainer(ctx, string(containerID)); err != nil {
-// 			log.Error().Err(err).Msg("failed to cleanup zdb container")
-// 		}
-// 	}
-
-// 	return nil
-// }
-
-// // checks running containers for subvolumes that might need to be saved because they are used
-// // and subvolumes that might need to be deleted because they have no attached container anymore
-// func (j *janitorImpl) protectedVolumesFromContainers(ctx context.Context) (map[string]struct{}, error) {
-// 	toSave := make(map[string]struct{})
-
-// 	contd := stubs.NewContainerModuleStub(j.zbus)
-
-// 	cNamespaces, err := contd.ListNS()
-// 	if err != nil {
-// 		log.Err(err).Msgf("failed to list namespaces")
-// 		return nil, err
-// 	}
-
-// 	for _, ns := range cNamespaces {
-// 		containerIDs, err := contd.List(ns)
-// 		if err != nil {
-// 			log.Error().Err(err).Msg("failed to list container IDs")
-// 			return nil, err
-// 		}
-
-// 		for _, id := range containerIDs {
-// 			info, err := contd.Inspect(ns, id)
-// 			if err != nil {
-// 				log.Error().Err(err).Msgf("failed to inspect container %s", id)
-// 				continue
-// 			}
-
-// 			// avoid to remove any used subvolume used by flistd for root container fs
-// 			toSave[info.RootFS] = struct{}{}
-
-// 			for _, mnt := range info.Mounts {
-// 				// the container has many other things in info.Mounts
-// 				// that are not volumes so we are only interested
-// 				// to volumes from zos
-// 				if !strings.HasPrefix(mnt.Source, "/mnt/") {
-// 					continue
-// 				}
-
-// 				toSave[mnt.Source] = struct{}{}
-// 			}
-// 		}
-// 	}
-
-// 	return toSave, nil
-// }
-
-// func newZdbConnection(id string) (zdb.Client, error) {
-// 	socket := fmt.Sprintf("unix://%s/zdb.sock", socketDir(pkg.ContainerID(id)))
-// 	cl := zdb.New(socket)
-// 	return cl, cl.Connect()
-// }
diff --git a/pkg/primitives/crypto.go b/pkg/primitives/crypto.go
deleted file mode 100644
index fddb9eec..00000000
--- a/pkg/primitives/crypto.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package primitives
-
-// func (p *Primitives) decryptSecret(ctx context.Context, user uint32, secret string, version int) (string, error) {
-// 	if len(secret) == 0 {
-// 		return "", nil
-// 	}
-
-// 	engine := provision.GetEngine(ctx)
-
-// 	identity := stubs.NewIdentityManagerStub(p.zbus)
-
-// 	bytes, err := hex.DecodeString(secret)
-// 	if err != nil {
-// 		return "", err
-// 	}
-
-// 	var (
-// 		out []byte
-// 	)
-// 	// now only one version is supported
-// 	switch version {
-// 	default:
-// 		var userPubKey ed25519.PublicKey
-// 		userPubKey, err = engine.Twins().GetKey(user)
-// 		if err != nil || userPubKey == nil {
-// 			return "", fmt.Errorf("failed to retrieve user %d public key: %s", user, err)
-// 		}
-// 		out, err = identity.DecryptECDH(bytes, userPubKey)
-// 	}
-
-// 	return string(out), err
-// }
diff --git a/pkg/primitives/filesystem.go b/pkg/primitives/filesystem.go
deleted file mode 100644
index 44ffec23..00000000
--- a/pkg/primitives/filesystem.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package primitives
-
-import (
-	"github.com/threefoldtech/zos/pkg/gridtypes"
-)
-
-// FilesystemName return a string to be used as filesystem name from
-// a reservation object
-func FilesystemName(wl *gridtypes.WorkloadWithID) string {
-	return wl.ID.String()
-}
diff --git a/pkg/primitives/gatewayfqdn.go b/pkg/primitives/gateway/gatewayfqdn.go
similarity index 67%
rename from pkg/primitives/gatewayfqdn.go
rename to pkg/primitives/gateway/gatewayfqdn.go
index 5d7b120c..d59be448 100644
--- a/pkg/primitives/gatewayfqdn.go
+++ b/pkg/primitives/gateway/gatewayfqdn.go
@@ -1,4 +1,4 @@
-package primitives
+package gateway
 
 import (
 	"context"
@@ -6,12 +6,26 @@ import (
 	"fmt"
 
 	"github.com/pkg/errors"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
+	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
-func (p *Primitives) gwFQDNProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*FQDNManager)(nil)
+)
+
+type FQDNManager struct {
+	zbus zbus.Client
+}
+
+func NewFQDNManager(zbus zbus.Client) *FQDNManager {
+	return &FQDNManager{zbus}
+}
+
+func (p *FQDNManager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	result := zos.GatewayFQDNResult{}
 	var proxy zos.GatewayFQDNProxy
 	if err := json.Unmarshal(wl.Data, &proxy); err != nil {
@@ -29,7 +43,7 @@ func (p *Primitives) gwFQDNProvision(ctx context.Context, wl *gridtypes.Workload
 	return result, nil
 }
 
-func (p *Primitives) gwFQDNDecommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *FQDNManager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	gateway := stubs.NewGatewayStub(p.zbus)
 	if err := gateway.DeleteNamedProxy(ctx, wl.ID.String()); err != nil {
 		return errors.Wrap(err, "failed to delete fqdn proxy")
diff --git a/pkg/primitives/gatewayname.go b/pkg/primitives/gateway/gatewayname.go
similarity index 73%
rename from pkg/primitives/gatewayname.go
rename to pkg/primitives/gateway/gatewayname.go
index 0cb75751..d97fa697 100644
--- a/pkg/primitives/gatewayname.go
+++ b/pkg/primitives/gateway/gatewayname.go
@@ -1,4 +1,4 @@
-package primitives
+package gateway
 
 import (
 	"context"
@@ -7,13 +7,26 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
-func (p *Primitives) gwProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*NameManager)(nil)
+)
+
+type NameManager struct {
+	zbus zbus.Client
+}
+
+func NewNameManager(zbus zbus.Client) *NameManager {
+	return &NameManager{zbus}
+}
+
+func (p *NameManager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 
 	result := zos.GatewayProxyResult{}
 	var proxy zos.GatewayNameProxy
@@ -35,7 +48,7 @@ func (p *Primitives) gwProvision(ctx context.Context, wl *gridtypes.WorkloadWith
 	return result, nil
 }
 
-func (p *Primitives) gwDecommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *NameManager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	gateway := stubs.NewGatewayStub(p.zbus)
 	if err := gateway.DeleteNamedProxy(ctx, wl.ID.String()); err != nil {
 		return errors.Wrap(err, "failed to delete name proxy")
diff --git a/pkg/primitives/network.go b/pkg/primitives/network/network.go
similarity index 67%
rename from pkg/primitives/network.go
rename to pkg/primitives/network/network.go
index 979d7f6a..730fc4ac 100644
--- a/pkg/primitives/network.go
+++ b/pkg/primitives/network/network.go
@@ -1,4 +1,4 @@
-package primitives
+package network
 
 import (
 	"context"
@@ -8,6 +8,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
 
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
@@ -15,8 +16,21 @@ import (
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
+var (
+	_ provision.Manager = (*Manager)(nil)
+	_ provision.Updater = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
 // networkProvision is entry point to provision a network
-func (p *Primitives) networkProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) networkProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	twin, _ := provision.GetDeploymentID(ctx)
 
 	var network zos.Network
@@ -39,11 +53,15 @@ func (p *Primitives) networkProvisionImpl(ctx context.Context, wl *gridtypes.Wor
 	return nil
 }
 
-func (p *Primitives) networkProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+	return nil, p.networkProvisionImpl(ctx, wl)
+}
+
+func (p *Manager) Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	return nil, p.networkProvisionImpl(ctx, wl)
 }
 
-func (p *Primitives) networkDecommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	mgr := stubs.NewNetworkerStub(p.zbus)
 
 	var network zos.Network
diff --git a/pkg/primitives/provisioner.go b/pkg/primitives/provisioner.go
index 27b259cc..8ec4824a 100644
--- a/pkg/primitives/provisioner.go
+++ b/pkg/primitives/provisioner.go
@@ -4,58 +4,31 @@ import (
 	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
+	"github.com/threefoldtech/zos/pkg/primitives/gateway"
+	"github.com/threefoldtech/zos/pkg/primitives/network"
+	"github.com/threefoldtech/zos/pkg/primitives/pubip"
+	"github.com/threefoldtech/zos/pkg/primitives/qsfs"
+	"github.com/threefoldtech/zos/pkg/primitives/vm"
+	"github.com/threefoldtech/zos/pkg/primitives/zdb"
+	"github.com/threefoldtech/zos/pkg/primitives/zlogs"
+	"github.com/threefoldtech/zos/pkg/primitives/zmount"
 	"github.com/threefoldtech/zos/pkg/provision"
 )
 
-// Primitives hold all the logic responsible to provision and decomission
-// the different primitives workloads defined by this package
-type Primitives struct {
-	provision.Provisioner
-	zbus zbus.Client
-}
-
-var _ provision.Provisioner = (*Primitives)(nil)
-
 // NewPrimitivesProvisioner creates a new 0-OS provisioner
-func NewPrimitivesProvisioner(zbus zbus.Client) *Primitives {
-	p := &Primitives{
-		zbus: zbus,
-	}
-
-	provisioners := map[gridtypes.WorkloadType]provision.DeployFunction{
-		zos.ZMountType:           p.zMountProvision,
-		zos.NetworkType:          p.networkProvision,
-		zos.ZDBType:              p.zdbProvision,
-		zos.ZMachineType:         p.virtualMachineProvision,
-		zos.PublicIPv4Type:       p.publicIPProvision,
-		zos.PublicIPType:         p.publicIPProvision,
-		zos.GatewayNameProxyType: p.gwProvision,
-		zos.GatewayFQDNProxyType: p.gwFQDNProvision,
-		zos.QuantumSafeFSType:    p.qsfsProvision,
-		zos.ZLogsType:            p.zlogsProvision,
+func NewPrimitivesProvisioner(zbus zbus.Client) provision.Provisioner {
+	managers := map[gridtypes.WorkloadType]provision.Manager{
+		zos.ZMountType:           zmount.NewManager(zbus),
+		zos.ZLogsType:            zlogs.NewManager(zbus),
+		zos.QuantumSafeFSType:    qsfs.NewManager(zbus),
+		zos.ZDBType:              zdb.NewManager(zbus),
+		zos.NetworkType:          network.NewManager(zbus),
+		zos.PublicIPType:         pubip.NewManager(zbus),
+		zos.PublicIPv4Type:       pubip.NewManager(zbus), // backward compatibility
+		zos.ZMachineType:         vm.NewManager(zbus),
+		zos.GatewayNameProxyType: gateway.NewNameManager(zbus),
+		zos.GatewayFQDNProxyType: gateway.NewFQDNManager(zbus),
 	}
-	decommissioners := map[gridtypes.WorkloadType]provision.RemoveFunction{
-		zos.ZMountType:           p.zMountDecommission,
-		zos.NetworkType:          p.networkDecommission,
-		zos.ZDBType:              p.zdbDecommission,
-		zos.ZMachineType:         p.vmDecomission,
-		zos.PublicIPv4Type:       p.publicIPDecomission,
-		zos.PublicIPType:         p.publicIPDecomission,
-		zos.GatewayNameProxyType: p.gwDecommission,
-		zos.GatewayFQDNProxyType: p.gwFQDNDecommission,
-		zos.QuantumSafeFSType:    p.qsfsDecommision,
-		zos.ZLogsType:            p.zlogsDecomission,
-	}
-
-	// only network support update atm
-	updaters := map[gridtypes.WorkloadType]provision.DeployFunction{
-		zos.NetworkType:       p.networkProvision,
-		zos.QuantumSafeFSType: p.qsfsUpdate,
-		zos.ZDBType:           p.zdbUpdate,
-		zos.ZMountType:        p.zMountUpdate,
-	}
-
-	p.Provisioner = provision.NewMapProvisioner(provisioners, decommissioners, updaters)
 
-	return p
+	return provision.NewMapProvisioner(managers)
 }
diff --git a/pkg/primitives/public_ip.go b/pkg/primitives/pubip/public_ip.go
similarity index 78%
rename from pkg/primitives/public_ip.go
rename to pkg/primitives/pubip/public_ip.go
index 6625bda7..2094aef8 100644
--- a/pkg/primitives/public_ip.go
+++ b/pkg/primitives/pubip/public_ip.go
@@ -1,4 +1,4 @@
-package primitives
+package pubip
 
 import (
 	"context"
@@ -10,6 +10,7 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
@@ -17,11 +18,23 @@ import (
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
-func (p *Primitives) publicIPProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	return p.publicIPProvisionImpl(ctx, wl)
 }
 
-func (p *Primitives) getAssignedPublicIP(ctx context.Context, wl *gridtypes.WorkloadWithID) (ip gridtypes.IPNet, gw net.IP, err error) {
+func (p *Manager) getAssignedPublicIP(ctx context.Context, wl *gridtypes.WorkloadWithID) (ip gridtypes.IPNet, gw net.IP, err error) {
 	//Okay, this implementation is tricky but will be simplified once we store the reserved IP
 	//on the contract.
 
@@ -79,7 +92,7 @@ func (p *Primitives) getAssignedPublicIP(ctx context.Context, wl *gridtypes.Work
 			continue
 		}
 
-		used, err := p.getPubIPConfig(ipWl)
+		used, err := GetPubIPConfig(ipWl)
 		if err != nil {
 			return ip, gw, err
 		}
@@ -107,7 +120,7 @@ func (p *Primitives) getAssignedPublicIP(ctx context.Context, wl *gridtypes.Work
 	return ip, gw, fmt.Errorf("could not allocate public IP address to workload")
 }
 
-func (p *Primitives) getPublicIPData(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIP, err error) {
+func (p *Manager) getPublicIPData(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIP, err error) {
 	switch wl.Type {
 	case zos.PublicIPv4Type:
 		// backword compatibility with older ipv4 type
@@ -121,13 +134,13 @@ func (p *Primitives) getPublicIPData(ctx context.Context, wl *gridtypes.Workload
 	return
 }
 
-func (p *Primitives) publicIPProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
+func (p *Manager) publicIPProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
 	config, err := p.getPublicIPData(ctx, wl)
 	if err != nil {
 		return zos.PublicIPResult{}, err
 	}
 
-	tapName := tapNameFromName(wl.ID, "pub")
+	tapName := wl.ID.Unique("pub")
 	network := stubs.NewNetworkerStub(p.zbus)
 	fName := filterName(tapName)
 
@@ -169,10 +182,10 @@ func (p *Primitives) publicIPProvisionImpl(ctx context.Context, wl *gridtypes.Wo
 	return
 }
 
-func (p *Primitives) publicIPDecomission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	// Disconnect the public interface from the network if one exists
 	network := stubs.NewNetworkerStub(p.zbus)
-	tapName := tapNameFromName(wl.ID, "pub")
+	tapName := wl.ID.Unique("pub")
 	fName := filterName(tapName)
 	if err := network.RemovePubIPFilter(ctx, fName); err != nil {
 		log.Error().Err(err).Msg("could not remove filter rules")
@@ -207,3 +220,20 @@ func predictedSlaac(base net.IPNet, mac string) (gridtypes.IPNet, error) {
 	return gridtypes.IPNet{IPNet: base}, nil
 
 }
+
+// GetPubIPConfig get the public ip, and the gateway from the workload
+func GetPubIPConfig(wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
+	if wl.Type != zos.PublicIPv4Type && wl.Type != zos.PublicIPType {
+		return result, fmt.Errorf("workload for public IP is of wrong type")
+	}
+
+	if wl.Result.State != gridtypes.StateOk {
+		return result, fmt.Errorf("public ip workload is not okay")
+	}
+
+	if err := wl.Result.Unmarshal(&result); err != nil {
+		return result, errors.Wrap(err, "failed to load ip result")
+	}
+
+	return result, nil
+}
diff --git a/pkg/primitives/qsfs.go b/pkg/primitives/qsfs/qsfs.go
similarity index 69%
rename from pkg/primitives/qsfs.go
rename to pkg/primitives/qsfs/qsfs.go
index 1b2615c8..b7342584 100644
--- a/pkg/primitives/qsfs.go
+++ b/pkg/primitives/qsfs/qsfs.go
@@ -1,4 +1,4 @@
-package primitives
+package qsfs
 
 import (
 	"context"
@@ -6,12 +6,27 @@ import (
 	"fmt"
 
 	"github.com/pkg/errors"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
+	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
-func (p *Primitives) qsfsProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*Manager)(nil)
+	_ provision.Updater = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	var result zos.QuatumSafeFSResult
 	var proxy zos.QuantumSafeFS
 	if err := json.Unmarshal(wl.Data, &proxy); err != nil {
@@ -27,7 +42,7 @@ func (p *Primitives) qsfsProvision(ctx context.Context, wl *gridtypes.WorkloadWi
 	return result, nil
 }
 
-func (p *Primitives) qsfsDecommision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	qsfs := stubs.NewQSFSDStub(p.zbus)
 	err := qsfs.SignalDelete(ctx, wl.ID.String())
 	if err != nil {
@@ -36,7 +51,7 @@ func (p *Primitives) qsfsDecommision(ctx context.Context, wl *gridtypes.Workload
 	return nil
 }
 
-func (p *Primitives) qsfsUpdate(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+func (p *Manager) Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	var result zos.QuatumSafeFSResult
 	var proxy zos.QuantumSafeFS
 	if err := json.Unmarshal(wl.Data, &proxy); err != nil {
diff --git a/pkg/primitives/statistics.go b/pkg/primitives/statistics.go
index 0840ed23..5e53afe0 100644
--- a/pkg/primitives/statistics.go
+++ b/pkg/primitives/statistics.go
@@ -30,6 +30,10 @@ func GetCapacity(ctx context.Context) gridtypes.Capacity {
 	return val.(gridtypes.Capacity)
 }
 
+var (
+	_ provision.Provisioner = (*Statistics)(nil)
+)
+
 // Statistics a provisioner interceptor that keeps track
 // of consumed capacity. It also does validate of required
 // capacity and then can report that this capacity can not be fulfilled
@@ -139,6 +143,11 @@ func (s *Statistics) hasEnoughCapacity(required *gridtypes.Capacity) error {
 	return nil
 }
 
+// Initialize implements provisioner interface
+func (s *Statistics) Initialize(ctx context.Context) error {
+	return s.inner.Initialize(ctx)
+}
+
 // Provision implements the provisioner interface
 func (s *Statistics) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (result gridtypes.Result, err error) {
 	current := s.Current()
@@ -163,7 +172,7 @@ func (s *Statistics) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID
 		return result, err
 	}
 
-	if result.State == gridtypes.StateOk {
+	if result.State.IsOkay() {
 		log.Debug().Str("type", wl.Type.String()).Str("id", wl.ID.String()).Msgf("incrmenting capacity +%+v", needed)
 		s.counters.Increment(needed)
 	}
@@ -172,8 +181,8 @@ func (s *Statistics) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID
 }
 
 // Decommission implements the decomission interface
-func (s *Statistics) Decommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
-	if err := s.inner.Decommission(ctx, wl); err != nil {
+func (s *Statistics) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	if err := s.inner.Deprovision(ctx, wl); err != nil {
 		return err
 	}
 	cap, err := wl.Capacity()
@@ -197,6 +206,14 @@ func (s *Statistics) CanUpdate(ctx context.Context, typ gridtypes.WorkloadType)
 	return s.inner.CanUpdate(ctx, typ)
 }
 
+func (s *Statistics) Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error) {
+	return s.inner.Pause(ctx, wl)
+}
+
+func (s *Statistics) Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error) {
+	return s.inner.Resume(ctx, wl)
+}
+
 // statistics api handlers for msgbus
 type statisticsMessageBus struct {
 	stats *Statistics
diff --git a/pkg/primitives/vm/pause.go b/pkg/primitives/vm/pause.go
new file mode 100644
index 00000000..ec0e445e
--- /dev/null
+++ b/pkg/primitives/vm/pause.go
@@ -0,0 +1,29 @@
+package vm
+
+import (
+	"context"
+
+	"github.com/threefoldtech/zos/pkg/gridtypes"
+	"github.com/threefoldtech/zos/pkg/provision"
+	"github.com/threefoldtech/zos/pkg/stubs"
+)
+
+func (m *Manager) Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	vm := stubs.NewVMModuleStub(m.zbus)
+
+	if err := vm.Lock(ctx, wl.ID.String(), true); err != nil {
+		return provision.UnChanged(err)
+	}
+
+	return provision.Paused()
+}
+
+func (m *Manager) Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	vm := stubs.NewVMModuleStub(m.zbus)
+
+	if err := vm.Lock(ctx, wl.ID.String(), false); err != nil {
+		return provision.UnChanged(err)
+	}
+
+	return provision.Ok()
+}
diff --git a/pkg/primitives/vm_utils.go b/pkg/primitives/vm/utils.go
similarity index 83%
rename from pkg/primitives/vm_utils.go
rename to pkg/primitives/vm/utils.go
index 005f17a5..43dfc5d5 100644
--- a/pkg/primitives/vm_utils.go
+++ b/pkg/primitives/vm/utils.go
@@ -1,4 +1,4 @@
-package primitives
+package vm
 
 import (
 	"context"
@@ -14,6 +14,7 @@ import (
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
+	"github.com/threefoldtech/zos/pkg/primitives/pubip"
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
@@ -24,12 +25,12 @@ var (
 	}
 )
 
-func (p *Primitives) newYggNetworkInterface(ctx context.Context, wl *gridtypes.WorkloadWithID) (pkg.VMIface, error) {
+func (p *Manager) newYggNetworkInterface(ctx context.Context, wl *gridtypes.WorkloadWithID) (pkg.VMIface, error) {
 	network := stubs.NewNetworkerStub(p.zbus)
 
 	//TODO: if we use `ygg` as a network name. this will conflict
 	//if the user has a network that is called `ygg`.
-	tapName := tapNameFromName(wl.ID, "ygg")
+	tapName := wl.ID.Unique("ygg")
 	iface, err := network.SetupYggTap(ctx, tapName)
 	if err != nil {
 		return pkg.VMIface{}, errors.Wrap(err, "could not set up tap device")
@@ -56,7 +57,7 @@ func (p *Primitives) newYggNetworkInterface(ctx context.Context, wl *gridtypes.W
 	return out, nil
 }
 
-func (p *Primitives) newPrivNetworkInterface(ctx context.Context, dl gridtypes.Deployment, wl *gridtypes.WorkloadWithID, inf zos.MachineInterface) (pkg.VMIface, error) {
+func (p *Manager) newPrivNetworkInterface(ctx context.Context, dl gridtypes.Deployment, wl *gridtypes.WorkloadWithID, inf zos.MachineInterface) (pkg.VMIface, error) {
 	network := stubs.NewNetworkerStub(p.zbus)
 	netID := zos.NetworkID(dl.TwinID, inf.Network)
 
@@ -89,7 +90,7 @@ func (p *Primitives) newPrivNetworkInterface(ctx context.Context, dl gridtypes.D
 		return pkg.VMIface{}, errors.Wrap(err, "could not convert private ipv4 to ipv6")
 	}
 
-	tapName := tapNameFromName(wl.ID, string(inf.Network))
+	tapName := wl.ID.Unique(string(inf.Network))
 	iface, err := network.SetupPrivTap(ctx, netID, tapName)
 	if err != nil {
 		return pkg.VMIface{}, errors.Wrap(err, "could not set up tap device")
@@ -117,16 +118,16 @@ func (p *Primitives) newPrivNetworkInterface(ctx context.Context, dl gridtypes.D
 	return out, nil
 }
 
-func (p *Primitives) newPubNetworkInterface(ctx context.Context, deployment gridtypes.Deployment, cfg ZMachine) (pkg.VMIface, error) {
+func (p *Manager) newPubNetworkInterface(ctx context.Context, deployment gridtypes.Deployment, cfg ZMachine) (pkg.VMIface, error) {
 	network := stubs.NewNetworkerStub(p.zbus)
 	ipWl, err := deployment.Get(cfg.Network.PublicIP)
 	if err != nil {
 		return pkg.VMIface{}, err
 	}
 
-	tapName := tapNameFromName(ipWl.ID, "pub")
+	tapName := ipWl.ID.Unique("pub")
 
-	config, err := p.getPubIPConfig(ipWl)
+	config, err := pubip.GetPubIPConfig(ipWl)
 	if err != nil {
 		return pkg.VMIface{}, errors.Wrap(err, "could not get public ip config")
 	}
@@ -165,23 +166,6 @@ func (p *Primitives) newPubNetworkInterface(ctx context.Context, deployment grid
 	}, nil
 }
 
-// Get the public ip, and the gateway from the reservation ID
-func (p *Primitives) getPubIPConfig(wl *gridtypes.WorkloadWithID) (result zos.PublicIPResult, err error) {
-	if wl.Type != zos.PublicIPv4Type && wl.Type != zos.PublicIPType {
-		return result, fmt.Errorf("workload for public IP is of wrong type")
-	}
-
-	if wl.Result.State != gridtypes.StateOk {
-		return result, fmt.Errorf("public ip workload is not okay")
-	}
-
-	if err := wl.Result.Unmarshal(&result); err != nil {
-		return result, errors.Wrap(err, "failed to load ip result")
-	}
-
-	return result, nil
-}
-
 func getFlistInfo(imagePath string) (FListInfo, error) {
 	image := imagePath + "/image.raw"
 	log.Debug().Str("file", image).Msg("checking image")
diff --git a/pkg/primitives/vm.go b/pkg/primitives/vm/vm.go
similarity index 89%
rename from pkg/primitives/vm.go
rename to pkg/primitives/vm/vm.go
index a6ac1352..580bf9ca 100644
--- a/pkg/primitives/vm.go
+++ b/pkg/primitives/vm/vm.go
@@ -1,8 +1,7 @@
-package primitives
+package vm
 
 import (
 	"context"
-	"crypto/md5"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -11,9 +10,9 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/jbenet/go-base58"
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
@@ -42,11 +41,23 @@ func (t *FListInfo) IsContainer() bool {
 	return len(t.ImagePath) == 0
 }
 
-func (p *Primitives) virtualMachineProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	return p.virtualMachineProvisionImpl(ctx, wl)
 }
 
-func (p *Primitives) vmMounts(ctx context.Context, deployment gridtypes.Deployment, mounts []zos.MachineMount, format bool, vm *pkg.VM) error {
+func (p *Manager) vmMounts(ctx context.Context, deployment gridtypes.Deployment, mounts []zos.MachineMount, format bool, vm *pkg.VM) error {
 	for _, mount := range mounts {
 		wl, err := deployment.Get(mount.Name)
 		if err != nil {
@@ -71,7 +82,7 @@ func (p *Primitives) vmMounts(ctx context.Context, deployment gridtypes.Deployme
 	return nil
 }
 
-func (p *Primitives) mountDisk(ctx context.Context, wl *gridtypes.WorkloadWithID, mount zos.MachineMount, format bool, vm *pkg.VM) error {
+func (p *Manager) mountDisk(ctx context.Context, wl *gridtypes.WorkloadWithID, mount zos.MachineMount, format bool, vm *pkg.VM) error {
 	storage := stubs.NewStorageModuleStub(p.zbus)
 
 	info, err := storage.DiskLookup(ctx, wl.ID.String())
@@ -90,7 +101,7 @@ func (p *Primitives) mountDisk(ctx context.Context, wl *gridtypes.WorkloadWithID
 	return nil
 }
 
-func (p *Primitives) mountQsfs(wl *gridtypes.WorkloadWithID, mount zos.MachineMount, vm *pkg.VM) error {
+func (p *Manager) mountQsfs(wl *gridtypes.WorkloadWithID, mount zos.MachineMount, vm *pkg.VM) error {
 
 	var info zos.QuatumSafeFSResult
 	if err := wl.Result.Unmarshal(&info); err != nil {
@@ -101,7 +112,7 @@ func (p *Primitives) mountQsfs(wl *gridtypes.WorkloadWithID, mount zos.MachineMo
 	return nil
 }
 
-func (p *Primitives) virtualMachineProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.ZMachineResult, err error) {
+func (p *Manager) virtualMachineProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.ZMachineResult, err error) {
 	var (
 		storage = stubs.NewStorageModuleStub(p.zbus)
 		network = stubs.NewNetworkerStub(p.zbus)
@@ -166,7 +177,7 @@ func (p *Primitives) virtualMachineProvisionImpl(ctx context.Context, wl *gridty
 		if err != nil {
 			return result, err
 		}
-		ifs = append(ifs, tapNameFromName(wl.ID, string(nic.Network)))
+		ifs = append(ifs, wl.ID.Unique(string(nic.Network)))
 		networkInfo.Ifaces = append(networkInfo.Ifaces, inf)
 	}
 
@@ -179,7 +190,7 @@ func (p *Primitives) virtualMachineProvisionImpl(ctx context.Context, wl *gridty
 		}
 
 		ipWl, _ := deployment.Get(config.Network.PublicIP)
-		pubIf = tapNameFromName(ipWl.ID, "pub")
+		pubIf = ipWl.ID.Unique("pub")
 		networkInfo.Ifaces = append(networkInfo.Ifaces, inf)
 	}
 
@@ -190,7 +201,7 @@ func (p *Primitives) virtualMachineProvisionImpl(ctx context.Context, wl *gridty
 		}
 
 		log.Debug().Msgf("Planetary: %+v", inf)
-		ifs = append(ifs, tapNameFromName(wl.ID, "ygg"))
+		ifs = append(ifs, wl.ID.Unique("ygg"))
 		networkInfo.Ifaces = append(networkInfo.Ifaces, inf)
 		result.YggIP = inf.IPs[0].IP.String()
 	}
@@ -349,7 +360,7 @@ func (p *Primitives) virtualMachineProvisionImpl(ctx context.Context, wl *gridty
 
 	return result, err
 }
-func (p *Primitives) copyFile(srcPath string, destPath string, permissions os.FileMode) error {
+func (p *Manager) copyFile(srcPath string, destPath string, permissions os.FileMode) error {
 	src, err := os.Open(srcPath)
 	if err != nil {
 		return errors.Wrapf(err, "Coludn't find %s on the node", srcPath)
@@ -367,7 +378,7 @@ func (p *Primitives) copyFile(srcPath string, destPath string, permissions os.Fi
 	return nil
 }
 
-func (p *Primitives) vmDecomission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	var (
 		flist   = stubs.NewFlisterStub(p.zbus)
 		network = stubs.NewNetworkerStub(p.zbus)
@@ -397,7 +408,7 @@ func (p *Primitives) vmDecomission(ctx context.Context, wl *gridtypes.WorkloadWi
 	}
 
 	for _, inf := range cfg.Network.Interfaces {
-		tapName := tapNameFromName(wl.ID, string(inf.Network))
+		tapName := wl.ID.Unique(string(inf.Network))
 
 		if err := network.RemoveTap(ctx, tapName); err != nil {
 			return errors.Wrap(err, "could not clean up tap device")
@@ -405,7 +416,7 @@ func (p *Primitives) vmDecomission(ctx context.Context, wl *gridtypes.WorkloadWi
 	}
 
 	if cfg.Network.Planetary {
-		tapName := tapNameFromName(wl.ID, "ygg")
+		tapName := wl.ID.Unique("ygg")
 		if err := network.RemoveTap(ctx, tapName); err != nil {
 			return errors.Wrap(err, "could not clean up tap device")
 		}
@@ -418,7 +429,7 @@ func (p *Primitives) vmDecomission(ctx context.Context, wl *gridtypes.WorkloadWi
 		if err != nil {
 			return err
 		}
-		ifName := tapNameFromName(ipWl.ID, "pub")
+		ifName := ipWl.ID.Unique("pub")
 		if err := network.RemovePubTap(ctx, ifName); err != nil {
 			return errors.Wrap(err, "could not clean up public tap device")
 		}
@@ -426,16 +437,3 @@ func (p *Primitives) vmDecomission(ctx context.Context, wl *gridtypes.WorkloadWi
 
 	return nil
 }
-
-func tapNameFromName(id gridtypes.WorkloadID, network string) string {
-	m := md5.New()
-
-	fmt.Fprintf(m, "%s:%s", id.String(), network)
-
-	h := m.Sum(nil)
-	b := base58.Encode(h[:])
-	if len(b) > 13 {
-		b = b[:13]
-	}
-	return string(b)
-}
diff --git a/pkg/primitives/zdb.go b/pkg/primitives/zdb/zdb.go
similarity index 84%
rename from pkg/primitives/zdb.go
rename to pkg/primitives/zdb/zdb.go
index 1b770689..8a09b6ef 100644
--- a/pkg/primitives/zdb.go
+++ b/pkg/primitives/zdb/zdb.go
@@ -1,4 +1,4 @@
-package primitives
+package zdb
 
 import (
 	"context"
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/cenkalti/backoff/v3"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 	"github.com/threefoldtech/zos/pkg/provision"
@@ -71,12 +72,27 @@ func (z *tZDBContainer) DataMount() (string, error) {
 	return "", fmt.Errorf("container '%s' does not have a valid data mount", z.Name)
 }
 
-func (p *Primitives) zdbProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager     = (*Manager)(nil)
+	_ provision.Updater     = (*Manager)(nil)
+	_ provision.Initializer = (*Manager)(nil)
+	_ provision.Pauser      = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	res, err := p.zdbProvisionImpl(ctx, wl)
 	return res, newSafeError(err)
 }
 
-func (p *Primitives) zdbListContainers(ctx context.Context) (map[pkg.ContainerID]tZDBContainer, error) {
+func (p *Manager) zdbListContainers(ctx context.Context) (map[pkg.ContainerID]tZDBContainer, error) {
 	var (
 		contmod = stubs.NewContainerModuleStub(p.zbus)
 	)
@@ -108,7 +124,7 @@ func (p *Primitives) zdbListContainers(ctx context.Context) (map[pkg.ContainerID
 	return m, nil
 }
 
-func (p *Primitives) zdbProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (zos.ZDBResult, error) {
+func (p *Manager) zdbProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (zos.ZDBResult, error) {
 	var (
 		//contmod = stubs.NewContainerModuleStub(p.zbus)
 		storage = stubs.NewStorageModuleStub(p.zbus)
@@ -207,7 +223,7 @@ func (p *Primitives) zdbProvisionImpl(ctx context.Context, wl *gridtypes.Workloa
 	}, nil
 }
 
-func (p *Primitives) ensureZdbContainer(ctx context.Context, device pkg.Device) (tZDBContainer, error) {
+func (p *Manager) ensureZdbContainer(ctx context.Context, device pkg.Device) (tZDBContainer, error) {
 	var container = stubs.NewContainerModuleStub(p.zbus)
 
 	name := pkg.ContainerID(device.ID)
@@ -231,7 +247,7 @@ func (p *Primitives) ensureZdbContainer(ctx context.Context, device pkg.Device)
 
 }
 
-func (p *Primitives) zdbRootFS(ctx context.Context) (string, error) {
+func (p *Manager) zdbRootFS(ctx context.Context) (string, error) {
 	var flist = stubs.NewFlisterStub(p.zbus)
 	var err error
 	var rootFS string
@@ -253,7 +269,7 @@ func (p *Primitives) zdbRootFS(ctx context.Context) (string, error) {
 	return rootFS, nil
 }
 
-func (p *Primitives) createZdbContainer(ctx context.Context, device pkg.Device) error {
+func (p *Manager) createZdbContainer(ctx context.Context, device pkg.Device) error {
 	var (
 		name       = pkg.ContainerID(device.ID)
 		cont       = stubs.NewContainerModuleStub(p.zbus)
@@ -336,7 +352,7 @@ func (p *Primitives) createZdbContainer(ctx context.Context, device pkg.Device)
 // dataMigration will make sure that we delete any data files from v1. This is
 // hardly a data migration but at this early stage it's fine since there is still
 // no real data loads live on the grid. All v2 zdbs, will be safe.
-func (p *Primitives) dataMigration(ctx context.Context, volume string) {
+func (p *Manager) dataMigration(ctx context.Context, volume string) {
 	v1, _ := zdb.IsZDBVersion1(ctx, volume)
 	// TODO: what if there is an error?
 	if !v1 {
@@ -352,7 +368,7 @@ func (p *Primitives) dataMigration(ctx context.Context, volume string) {
 	}
 }
 
-func (p *Primitives) zdbRun(ctx context.Context, name string, rootfs string, cmd string, netns string, volumepath string, socketdir string) error {
+func (p *Manager) zdbRun(ctx context.Context, name string, rootfs string, cmd string, netns string, volumepath string, socketdir string) error {
 	var cont = stubs.NewContainerModuleStub(p.zbus)
 
 	// we do data migration here because this is called
@@ -386,7 +402,7 @@ func (p *Primitives) zdbRun(ctx context.Context, name string, rootfs string, cmd
 	return err
 }
 
-func (p *Primitives) waitZDBIPs(ctx context.Context, namespace string, created time.Time) ([]net.IP, error) {
+func (p *Manager) waitZDBIPs(ctx context.Context, namespace string, created time.Time) ([]net.IP, error) {
 	// TODO: this method need to be abstracted, since it's now depends on the knewledge
 	// of the networking daemon internal (interfaces names)
 	// may be at least just get all ips from all interfaces inside the namespace
@@ -458,7 +474,7 @@ func (p *Primitives) waitZDBIPs(ctx context.Context, namespace string, created t
 	return containerIPs, nil
 }
 
-func (p *Primitives) createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) error {
+func (p *Manager) createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) error {
 	zdbCl := zdbConnection(containerID)
 	defer zdbCl.Close()
 	if err := zdbCl.Connect(); err != nil {
@@ -496,11 +512,11 @@ func (p *Primitives) createZDBNamespace(containerID pkg.ContainerID, nsID string
 	return nil
 }
 
-func (p *Primitives) zdbDecommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	return newSafeError(p.zdbDecommissionImpl(ctx, wl))
 }
 
-func (p *Primitives) zdbDecommissionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) zdbDecommissionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	containers, err := p.zdbListContainers(ctx)
 	if err != nil {
 		return errors.Wrap(err, "failed to list running zdbs")
@@ -536,12 +552,69 @@ func (p *Primitives) zdbDecommissionImpl(ctx context.Context, wl *gridtypes.Work
 	return nil
 }
 
-func (p *Primitives) zdbUpdate(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+func (p *Manager) findContainer(ctx context.Context, name string) (zdb.Client, error) {
+	containers, err := p.zdbListContainers(ctx)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to list running zdbs")
+	}
+
+	for id := range containers {
+		cl := zdbConnection(id)
+		if err := cl.Connect(); err != nil {
+			log.Error().Err(err).Str("id", string(id)).Msg("failed to connect to zdb instance")
+			continue
+		}
+
+		if ok, _ := cl.Exist(name); ok {
+			return cl, nil
+		}
+
+		_ = cl.Close()
+	}
+
+	return nil, os.ErrNotExist
+}
+
+func (p *Manager) Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	cl, err := p.findContainer(ctx, wl.ID.String())
+	if os.IsNotExist(err) {
+		return nil
+	} else if err != nil {
+		return provision.UnChanged(err)
+	}
+
+	defer cl.Close()
+
+	if err := cl.NamespaceSetLock(wl.ID.String(), true); err != nil {
+		return provision.UnChanged(errors.Wrap(err, "failed to set namespace locking"))
+	}
+
+	return provision.Paused()
+}
+
+func (p *Manager) Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	cl, err := p.findContainer(ctx, wl.ID.String())
+	if os.IsNotExist(err) {
+		return nil
+	} else if err != nil {
+		return provision.UnChanged(err)
+	}
+
+	defer cl.Close()
+
+	if err := cl.NamespaceSetLock(wl.ID.String(), false); err != nil {
+		return provision.UnChanged(errors.Wrap(err, "failed to set namespace locking"))
+	}
+
+	return provision.Ok()
+}
+
+func (p *Manager) Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	res, err := p.zdbUpdateImpl(ctx, wl)
 	return res, newSafeError(err)
 }
 
-func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.ZDBResult, err error) {
+func (p *Manager) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (result zos.ZDBResult, err error) {
 	current, err := provision.GetWorkload(ctx, wl.Name)
 	if err != nil {
 		// this should not happen but we need to have the check anyway
@@ -559,7 +632,7 @@ func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWi
 	}
 
 	if new.Mode != old.Mode {
-		return result, provision.NewUnchangedError(fmt.Errorf("cannot change namespace mode"))
+		return result, provision.UnChanged(fmt.Errorf("cannot change namespace mode"))
 	}
 
 	if new.Size < old.Size {
@@ -569,7 +642,7 @@ func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWi
 		// While this makes sense fro ZDB. it will not make zos able to calculate the namespace
 		// consumption because it will only count the size used by the workload from the user
 		// data but not actual size on disk. hence shrinking is not allowed.
-		return result, provision.NewUnchangedError(fmt.Errorf("cannot shrink zdb namespace"))
+		return result, provision.UnChanged(fmt.Errorf("cannot shrink zdb namespace"))
 	}
 
 	if new.Size == old.Size && new.Password == old.Password && new.Public == old.Public {
@@ -578,7 +651,7 @@ func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWi
 	}
 	containers, err := p.zdbListContainers(ctx)
 	if err != nil {
-		return result, provision.NewUnchangedError(errors.Wrap(err, "failed to list running zdbs"))
+		return result, provision.UnChanged(errors.Wrap(err, "failed to list running zdbs"))
 	}
 
 	name := wl.ID.String()
@@ -597,15 +670,15 @@ func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWi
 		if new.Size != old.Size {
 			free, reserved, err := reservedSpace(con)
 			if err != nil {
-				return result, provision.NewUnchangedError(errors.Wrap(err, "failed to calculate free/reserved space from zdb"))
+				return result, provision.UnChanged(errors.Wrap(err, "failed to calculate free/reserved space from zdb"))
 			}
 
 			if reserved+new.Size-old.Size > free {
-				return result, provision.NewUnchangedError(fmt.Errorf("no enough free space to support new size"))
+				return result, provision.UnChanged(fmt.Errorf("no enough free space to support new size"))
 			}
 
 			if err := con.NamespaceSetSize(name, uint64(new.Size)); err != nil {
-				return result, provision.NewUnchangedError(errors.Wrap(err, "failed to set new zdb namespace size"))
+				return result, provision.UnChanged(errors.Wrap(err, "failed to set new zdb namespace size"))
 			}
 		}
 
@@ -613,12 +686,12 @@ func (p *Primitives) zdbUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWi
 		// to setup the password
 		if new.Password != old.Password {
 			if err := con.NamespaceSetPassword(name, new.Password); err != nil {
-				return result, provision.NewUnchangedError(errors.Wrap(err, "failed to set new password"))
+				return result, provision.UnChanged(errors.Wrap(err, "failed to set new password"))
 			}
 		}
 		if new.Public != old.Public {
 			if err := con.NamespaceSetPublic(name, new.Public); err != nil {
-				return result, provision.NewUnchangedError(errors.Wrap(err, "failed to set public flag"))
+				return result, provision.UnChanged(errors.Wrap(err, "failed to set public flag"))
 			}
 		}
 
@@ -707,7 +780,7 @@ func isYgg(ip net.IP) bool {
 }
 
 // InitializeZDB makes sure all required zdbs are running
-func (p *Primitives) InitializeZDB(ctx context.Context) error {
+func (p *Manager) Initialize(ctx context.Context) error {
 	var (
 		storage  = stubs.NewStorageModuleStub(p.zbus)
 		contmod  = stubs.NewContainerModuleStub(p.zbus)
@@ -755,7 +828,7 @@ func (p *Primitives) InitializeZDB(ctx context.Context) error {
 	return nil
 }
 
-func (p *Primitives) upgradeRuntime(ctx context.Context, expected string, container pkg.ContainerID) error {
+func (p *Manager) upgradeRuntime(ctx context.Context, expected string, container pkg.ContainerID) error {
 	var (
 		flistmod = stubs.NewFlisterStub(p.zbus)
 		contmod  = stubs.NewContainerModuleStub(p.zbus)
diff --git a/pkg/primitives/zlogs.go b/pkg/primitives/zlogs/zlogs.go
similarity index 77%
rename from pkg/primitives/zlogs.go
rename to pkg/primitives/zlogs/zlogs.go
index 5e426180..c067f731 100644
--- a/pkg/primitives/zlogs.go
+++ b/pkg/primitives/zlogs/zlogs.go
@@ -1,4 +1,4 @@
-package primitives
+package zlogs
 
 import (
 	"context"
@@ -6,6 +6,7 @@ import (
 	"fmt"
 
 	"github.com/pkg/errors"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
@@ -13,8 +14,19 @@ import (
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
-func (p *Primitives) zlogsProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+var (
+	_ provision.Manager = (*Manager)(nil)
+)
+
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
 
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
 	var (
 		vm      = stubs.NewVMModuleStub(p.zbus)
 		network = stubs.NewNetworkerStub(p.zbus)
@@ -30,7 +42,7 @@ func (p *Primitives) zlogsProvision(ctx context.Context, wl *gridtypes.WorkloadW
 		return nil, errors.Wrapf(err, "no zmachine with name '%s'", cfg.ZMachine)
 	}
 
-	if !machine.Result.State.IsAny(gridtypes.StateOk) {
+	if !machine.Result.State.IsOkay() {
 		return nil, errors.Wrapf(err, "machine state is not ok")
 	}
 
@@ -57,7 +69,7 @@ func (p *Primitives) zlogsProvision(ctx context.Context, wl *gridtypes.WorkloadW
 
 }
 
-func (p *Primitives) zlogsDecomission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
 	var (
 		vm = stubs.NewVMModuleStub(p.zbus)
 	)
diff --git a/pkg/primitives/zmount.go b/pkg/primitives/zmount/zmount.go
similarity index 69%
rename from pkg/primitives/zmount.go
rename to pkg/primitives/zmount/zmount.go
index 08fd8320..15eb0468 100644
--- a/pkg/primitives/zmount.go
+++ b/pkg/primitives/zmount/zmount.go
@@ -1,4 +1,4 @@
-package primitives
+package zmount
 
 import (
 	"context"
@@ -7,19 +7,47 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/rs/zerolog/log"
+	"github.com/threefoldtech/zbus"
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 	"github.com/threefoldtech/zos/pkg/gridtypes/zos"
 	"github.com/threefoldtech/zos/pkg/provision"
 	"github.com/threefoldtech/zos/pkg/stubs"
 )
 
+var (
+	_ provision.Manager = (*Manager)(nil)
+	_ provision.Updater = (*Manager)(nil)
+)
+
 // ZMount defines a mount point
 type ZMount = zos.ZMount
 
 // ZMountResult types
 type ZMountResult = zos.ZMountResult
 
-func (p *Primitives) volumeProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (vol ZMountResult, err error) {
+type Manager struct {
+	zbus zbus.Client
+}
+
+func NewManager(zbus zbus.Client) *Manager {
+	return &Manager{zbus}
+}
+
+// VolumeProvision is entry point to provision a volume
+func (p *Manager) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+	return p.volumeProvisionImpl(ctx, wl)
+}
+
+func (p *Manager) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	vdisk := stubs.NewStorageModuleStub(p.zbus)
+	return vdisk.DiskDelete(ctx, wl.ID.String())
+}
+
+func (p *Manager) Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+	return p.zMountUpdateImpl(ctx, wl)
+}
+
+func (p *Manager) volumeProvisionImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (vol ZMountResult, err error) {
 	var config ZMount
 	if err := json.Unmarshal(wl.Data, &config); err != nil {
 		return ZMountResult{}, err
@@ -37,21 +65,7 @@ func (p *Primitives) volumeProvisionImpl(ctx context.Context, wl *gridtypes.Work
 }
 
 // VolumeProvision is entry point to provision a volume
-func (p *Primitives) zMountProvision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
-	return p.volumeProvisionImpl(ctx, wl)
-}
-
-func (p *Primitives) zMountDecommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
-	vdisk := stubs.NewStorageModuleStub(p.zbus)
-	return vdisk.DiskDelete(ctx, wl.ID.String())
-}
-
-func (p *Primitives) zMountUpdate(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
-	return p.zMountUpdateImpl(ctx, wl)
-}
-
-// VolumeProvision is entry point to provision a volume
-func (p *Primitives) zMountUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (vol ZMountResult, err error) {
+func (p *Manager) zMountUpdateImpl(ctx context.Context, wl *gridtypes.WorkloadWithID) (vol ZMountResult, err error) {
 	log.Debug().Msg("updating zmount")
 	current, err := provision.GetWorkload(ctx, wl.Name)
 	if err != nil {
@@ -72,31 +86,31 @@ func (p *Primitives) zMountUpdateImpl(ctx context.Context, wl *gridtypes.Workloa
 	if new.Size == old.Size {
 		return vol, provision.ErrNoActionNeeded
 	} else if new.Size < old.Size {
-		return vol, provision.NewUnchangedError(fmt.Errorf("not safe to shrink a disk"))
+		return vol, provision.UnChanged(fmt.Errorf("not safe to shrink a disk"))
 	}
 
 	// now validate that disk is not being used right now
 	deployment, err := provision.GetDeployment(ctx)
 	if err != nil {
-		return vol, provision.NewUnchangedError(errors.Wrap(err, "failed to get deployment"))
+		return vol, provision.UnChanged(errors.Wrap(err, "failed to get deployment"))
 	}
 
 	vms := deployment.ByType(zos.ZMachineType)
 	log.Debug().Int("count", len(vms)).Msg("found zmachines in deployment")
 	for _, vm := range vms {
 		// vm not running, no need to check
-		if !vm.Result.State.IsAny(gridtypes.StateOk) {
+		if !vm.Result.State.IsOkay() {
 			continue
 		}
 
 		var data zos.ZMachine
 		if err := json.Unmarshal(vm.Data, &data); err != nil {
-			return vol, provision.NewUnchangedError(errors.Wrap(err, "failed to load vm information"))
+			return vol, provision.UnChanged(errors.Wrap(err, "failed to load vm information"))
 		}
 
 		for _, mnt := range data.Mounts {
 			if mnt.Name == wl.Name {
-				return vol, provision.NewUnchangedError(fmt.Errorf("disk is mounted, please delete the VM first"))
+				return vol, provision.UnChanged(fmt.Errorf("disk is mounted, please delete the VM first"))
 			}
 		}
 	}
@@ -109,5 +123,5 @@ func (p *Primitives) zMountUpdateImpl(ctx context.Context, wl *gridtypes.Workloa
 	_, err = vdisk.DiskResize(ctx, wl.ID.String(), new.Size)
 	// we know it's safe to resize the disk, it won't break it so we
 	// can be sure we can wrap the error into an unchanged error
-	return vol, provision.NewUnchangedError(err)
+	return vol, provision.UnChanged(err)
 }
diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go
index 83094039..5b65b132 100644
--- a/pkg/provision/engine.go
+++ b/pkg/provision/engine.go
@@ -81,6 +81,10 @@ const (
 	// order of the workloads doesn't have to match
 	// the one sent by the user
 	opProvisionNoContract
+	// opPause, pauses a deployment
+	opPause
+	// opResume resumes a deployment
+	opResume
 )
 
 // engineJob is a persisted job instance that is
@@ -328,6 +332,46 @@ func (e *NativeEngine) Provision(ctx context.Context, deployment gridtypes.Deplo
 	return e.queue.Enqueue(&job)
 }
 
+// Pause deployment
+func (e *NativeEngine) Pause(ctx context.Context, twin uint32, id uint64) error {
+	deployment, err := e.storage.Get(twin, id)
+	if err != nil {
+		return err
+	}
+
+	log.Info().
+		Uint32("twin", deployment.TwinID).
+		Uint64("contract", deployment.ContractID).
+		Msg("schedule for pausing")
+
+	job := engineJob{
+		Target: deployment,
+		Op:     opPause,
+	}
+
+	return e.queue.Enqueue(&job)
+}
+
+// Resume deployment
+func (e *NativeEngine) Resume(ctx context.Context, twin uint32, id uint64) error {
+	deployment, err := e.storage.Get(twin, id)
+	if err != nil {
+		return err
+	}
+
+	log.Info().
+		Uint32("twin", deployment.TwinID).
+		Uint64("contract", deployment.ContractID).
+		Msg("schedule for pausing")
+
+	job := engineJob{
+		Target: deployment,
+		Op:     opResume,
+	}
+
+	return e.queue.Enqueue(&job)
+}
+
 // Deprovision workload
 func (e *NativeEngine) Deprovision(ctx context.Context, twin uint32, id uint64, reason string) error {
 	deployment, err := e.storage.Get(twin, id)
@@ -452,6 +496,10 @@ func (e *NativeEngine) Run(root context.Context) error {
 			e.installDeployment(ctx, &job.Target)
 		case opDeprovision:
 			e.uninstallDeployment(ctx, &job.Target, job.Message)
+		case opPause:
+			e.lockDeployment(ctx, &job.Target)
+		case opResume:
+			e.unlockDeployment(ctx, &job.Target)
 		case opUpdate:
 			// update is tricky because we need to work against
 			// 2 versions of the object. Once that reflects the current state
@@ -604,7 +652,7 @@ func (e *NativeEngine) uninstallWorkload(ctx context.Context, wl *gridtypes.Work
 		State: gridtypes.StateDeleted,
 		Error: reason,
 	}
-	if err := e.provisioner.Decommission(ctx, wl); err != nil {
+	if err := e.provisioner.Deprovision(ctx, wl); err != nil {
 		log.Error().Err(err).Stringer("id", wl.ID).Msg("failed to uninstall workload")
 		result.State = gridtypes.StateError
 		result.Error = err.Error()
@@ -707,6 +755,53 @@ func (e *NativeEngine) updateWorkload(ctx context.Context, wl *gridtypes.Workloa
 	return e.storage.Transaction(twin, deployment, wl.Workload.WithResults(result))
 }
 
+func (e *NativeEngine) lockWorkload(ctx context.Context, wl *gridtypes.WorkloadWithID, lock bool) error {
+	// this workload is already deleted or in error state
+	// we don't try again
+	twin, deployment, name, _ := wl.ID.Parts()
+
+	current, err := e.storage.Current(twin, deployment, name)
+	if err != nil {
+		// another error
+		return errors.Wrapf(err, "failed to get last transaction for '%s'", wl.ID.String())
+	} else {
+		if !current.Result.State.IsOkay() {
+			//nothing to do! it's either in error state or something else.
+			return nil
+		}
+	}
+
+	log := log.With().
+		Uint32("twin", twin).
+		Uint64("deployment", deployment).
+		Stringer("name", wl.Name).
+		Str("type", wl.Type.String()).
+		Bool("lock", lock).
+		Logger()
+
+	log.Debug().Msg("setting locking on workload")
+	action := e.provisioner.Resume
+	if lock {
+		action = e.provisioner.Pause
+	}
+	result, err := action(ctx, wl)
+	if errors.Is(err, ErrNoActionNeeded) {
+		// workload already exist, so no need to create a new transaction
+		return nil
+	} else if err != nil {
+		return err
+	}
+
+	if result.State == gridtypes.StateError {
+		log.Error().Str("error", result.Error).Msg("failed to set locking on workload")
+	}
+
+	return e.storage.Transaction(
+		twin,
+		deployment,
+		wl.Workload.WithResults(result))
+}
+
 func (e *NativeEngine) uninstallDeployment(ctx context.Context, getter gridtypes.WorkloadGetter, reason string) {
 	for i := len(e.order) - 1; i >= 0; i-- {
 		typ := e.order[i]
@@ -732,6 +827,32 @@ func (e *NativeEngine) installDeployment(ctx context.Context, getter gridtypes.W
 	}
 }
 
+func (e *NativeEngine) lockDeployment(ctx context.Context, getter gridtypes.WorkloadGetter) {
+	for i := len(e.order) - 1; i >= 0; i-- {
+		typ := e.order[i]
+
+		workloads := getter.ByType(typ)
+
+		for _, wl := range workloads {
+			if err := e.lockWorkload(ctx, wl, true); err != nil {
+				log.Error().Err(err).Stringer("id", wl.ID).Msg("failed to lock workload")
+			}
+		}
+	}
+}
+
+func (e *NativeEngine) unlockDeployment(ctx context.Context, getter gridtypes.WorkloadGetter) {
+	for _, typ := range e.order {
+		workloads := getter.ByType(typ)
+
+		for _, wl := range workloads {
+			if err := e.lockWorkload(ctx, wl, false); err != nil {
+				log.Error().Err(err).Stringer("id", wl.ID).Msg("failed to unlock workload")
+			}
+		}
+	}
+}
+
 // sortOperations sortes the operations, removes first in reverse type order, then upgrades/creates in type order
 func (e *NativeEngine) sortOperations(ops []gridtypes.UpgradeOp) {
 	// maps an operation to an integer, less comes first in sorting
diff --git a/pkg/provision/interface.go b/pkg/provision/interface.go
index ee72e552..34d8f6bb 100644
--- a/pkg/provision/interface.go
+++ b/pkg/provision/interface.go
@@ -19,6 +19,8 @@ type Engine interface {
 	// and will be processes later
 	Provision(ctx context.Context, wl gridtypes.Deployment) error
 	Deprovision(ctx context.Context, twin uint32, id uint64, reason string) error
+	Pause(ctx context.Context, twin uint32, id uint64) error
+	Resume(ctx context.Context, twin uint32, id uint64) error
 	Update(ctx context.Context, update gridtypes.Deployment) error
 	Storage() Storage
 	Twins() Twins
@@ -33,9 +35,19 @@ type Engine interface {
 // workload provision was not carried on because it's already deployed, basically a no action
 // needed indicator. In that case, the engine can ignore the returned result
 type Provisioner interface {
+	// Initialize is called before the provision engine is started
+	Initialize(ctx context.Context) error
+	// Provision a workload
 	Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error)
-	Decommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+	// Deprovision a workload
+	Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+	// Pause a workload
+	Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error)
+	// Resume a workload
+	Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error)
+	// Update a workload
 	Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error)
+	// CanUpdate checks if this workload can be updated on the fly
 	CanUpdate(ctx context.Context, typ gridtypes.WorkloadType) bool
 }
 
@@ -65,29 +77,6 @@ var (
 	ErrInvalidVersion = fmt.Errorf("invalid version")
 )
 
-// ErrUnchanged can be returned by the Provisioner.Update it means
-// that the update has failed but the workload is intact
-type ErrUnchanged struct {
-	cause error
-}
-
-// NewUnchangedError return an instance of ErrUnchanged
-func NewUnchangedError(cause error) error {
-	if cause == nil {
-		return nil
-	}
-
-	return ErrUnchanged{cause}
-}
-
-func (e ErrUnchanged) Unwrap() error {
-	return e.cause
-}
-
-func (e ErrUnchanged) Error() string {
-	return e.cause.Error()
-}
-
 // Field interface
 type Field interface{}
 type VersionField struct {
diff --git a/pkg/provision/provisiner.go b/pkg/provision/provisiner.go
index a70ce656..212c8340 100644
--- a/pkg/provision/provisiner.go
+++ b/pkg/provision/provisiner.go
@@ -10,92 +10,251 @@ import (
 	"github.com/threefoldtech/zos/pkg/gridtypes"
 )
 
-// DeployFunction simple provision function interface
-type DeployFunction func(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error)
+// Response interface for custom error responses
+// you never need to implement this interface
+// can only be returned by one of the methods in this
+// module.
 
-// RemoveFunction simple decommission function
-type RemoveFunction func(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+type Response interface {
+	error
+	state() gridtypes.ResultState
+	err() error
+}
+
+type response struct {
+	s gridtypes.ResultState
+	e error
+}
+
+func (r *response) Error() string {
+	if err := r.err(); err != nil {
+		return err.Error()
+	}
+
+	return ""
+}
+
+func (r *response) Unwrap() error {
+	return r.e
+}
+
+func (r *response) state() gridtypes.ResultState {
+	return r.s
+}
+
+func (r *response) err() error {
+	return r.e
+}
+
+// Ok response. you normally don't need to return
+// this from Manager methods. instead returning `nil` error
+// is preferred.
+func Ok() Response {
+	return &response{s: gridtypes.StateOk}
+}
+
+// UnChanged is a special response status that states that an operation has failed
+// but this did not affect the workload status. Usually during an update when the
+// update could not carried out, but the workload is still running correctly with
+// previous config
+func UnChanged(cause error) Response {
+	return &response{s: gridtypes.StateUnChanged, e: cause}
+}
+
+func Paused() Response {
+	return &response{s: gridtypes.StatePaused, e: fmt.Errorf("paused")}
+}
+
+// Manager defines basic type manager functionality. This interface
+// declares the provision and the deprovision method which is required
+// by any Type manager.
+type Manager interface {
+	Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error)
+	Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+}
+
+// Initializer interface define an extra Initialize method which is run on the provisioner
+// before the provision engine is started.
+type Initializer interface {
+	Initialize(ctx context.Context) error
+}
+
+// Updater defines the optional Update method for a type manager. Types are allowed
+// to implement update to change their settings on the fly
+type Updater interface {
+	Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error)
+}
+
+// Pauser defines optional Pause, Resume method for type managers. Types are allowed
+// to implement pause, resume to put the workload in paused state where it's not usable
+// by the user but at the same time not completely deleted.
+type Pauser interface {
+	Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+	Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) error
+}
 
 type mapProvisioner struct {
-	provisioners    map[gridtypes.WorkloadType]DeployFunction
-	decommissioners map[gridtypes.WorkloadType]RemoveFunction
-	updaters        map[gridtypes.WorkloadType]DeployFunction
+	managers map[gridtypes.WorkloadType]Manager
 }
 
 // NewMapProvisioner returns a new instance of a map provisioner
-func NewMapProvisioner(p map[gridtypes.WorkloadType]DeployFunction, d map[gridtypes.WorkloadType]RemoveFunction, u map[gridtypes.WorkloadType]DeployFunction) Provisioner {
+func NewMapProvisioner(managers map[gridtypes.WorkloadType]Manager) Provisioner {
 	return &mapProvisioner{
-		provisioners:    p,
-		decommissioners: d,
-		updaters:        u,
+		managers: managers,
+	}
+}
+
+func (p *mapProvisioner) Initialize(ctx context.Context) error {
+	for typ, mgr := range p.managers {
+		init, ok := mgr.(Initializer)
+		if !ok {
+			continue
+		}
+
+		if err := init.Initialize(ctx); err != nil {
+			return errors.Wrapf(err, "failed to run initializers for workload type '%s'", typ)
+		}
 	}
+
+	return nil
 }
 
 // Provision implements provision.Provisioner
 func (p *mapProvisioner) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (result gridtypes.Result, err error) {
-	handler, ok := p.provisioners[wl.Type]
+	manager, ok := p.managers[wl.Type]
 	if !ok {
-		return result, fmt.Errorf("no provisioner associated with workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+		return result, fmt.Errorf("unknown workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
 	}
 
-	data, err := handler(ctx, wl)
+	data, err := manager.Provision(ctx, wl)
 	if errors.Is(err, ErrNoActionNeeded) {
 		return result, err
 	}
 
-	return p.buildResult(data, err)
+	return buildResult(data, err)
 }
 
 // Decommission implementation for provision.Provisioner
-func (p *mapProvisioner) Decommission(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
-	handler, ok := p.decommissioners[wl.Type]
+func (p *mapProvisioner) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	manager, ok := p.managers[wl.Type]
 	if !ok {
-		return fmt.Errorf("no decomissioner associated with workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+		return fmt.Errorf("unknown workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
 	}
 
-	return handler(ctx, wl)
+	return manager.Deprovision(ctx, wl)
+}
+
+// Pause a workload
+func (p *mapProvisioner) Pause(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error) {
+	if wl.Result.State != gridtypes.StateOk {
+		return wl.Result, fmt.Errorf("can only pause workloads in ok state")
+	}
+
+	manager, ok := p.managers[wl.Type]
+	if !ok {
+		return wl.Result, fmt.Errorf("unknown workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+	}
+
+	// change all status to Paused
+	var err error = Paused()
+	// unless there is specific implementation to
+	// pause a work load, we call it.
+	mgr, ok := manager.(Pauser)
+	if ok {
+		err = mgr.Pause(ctx, wl)
+	}
+
+	// update the result object. this way we make sure data
+	// does not change across pause/resume changes
+	result := wl.Result
+	setState(&result, err)
+	return result, nil
+}
+
+// Resume a workload
+func (p *mapProvisioner) Resume(ctx context.Context, wl *gridtypes.WorkloadWithID) (gridtypes.Result, error) {
+	if wl.Result.State != gridtypes.StatePaused {
+		return wl.Result, fmt.Errorf("can only resume workloads in paused state")
+	}
+
+	manager, ok := p.managers[wl.Type]
+	if !ok {
+		return wl.Result, fmt.Errorf("unknown workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+	}
+	// change all status to Paused
+	var err error = Ok()
+	// unless there is specific implementation to
+	// pause a work load, we call it.
+	mgr, ok := manager.(Pauser)
+	if ok {
+		err = mgr.Resume(ctx, wl)
+	}
+
+	// update the result object. this way we make sure data
+	// does not change across pause/resume changes
+	result := wl.Result
+	setState(&result, err)
+	return result, nil
 }
 
 // Provision implements provision.Provisioner
 func (p *mapProvisioner) Update(ctx context.Context, wl *gridtypes.WorkloadWithID) (result gridtypes.Result, err error) {
-	handler, ok := p.updaters[wl.Type]
+	manager, ok := p.managers[wl.Type]
+	if !ok {
+		return result, fmt.Errorf("unknown workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+	}
+
+	updater, ok := manager.(Updater)
 	if !ok {
-		return result, fmt.Errorf("no updater associated with workload type '%s' for reservation id '%s'", wl.Type, wl.ID)
+		return result, fmt.Errorf("workload type '%s' does not support updating", wl.Type)
 	}
 
-	data, err := handler(ctx, wl)
+	data, err := updater.Update(ctx, wl)
 	if errors.Is(err, ErrNoActionNeeded) {
 		return result, err
 	}
 
-	return p.buildResult(data, err)
+	return buildResult(data, err)
 }
 
 func (p *mapProvisioner) CanUpdate(ctx context.Context, typ gridtypes.WorkloadType) bool {
-	_, ok := p.updaters[typ]
+	manager, ok := p.managers[typ]
+	if !ok {
+		return false
+	}
+
+	_, ok = manager.(Updater)
 	return ok
 }
 
-func (p *mapProvisioner) buildResult(data interface{}, err error) (gridtypes.Result, error) {
-	result := gridtypes.Result{
-		Created: gridtypes.Timestamp(time.Now().Unix()),
-	}
+func setState(result *gridtypes.Result, err error) {
+	result.Created = gridtypes.Timestamp(time.Now().Unix())
+	state := gridtypes.StateOk
+	str := ""
+
+	if err != nil {
+		str = err.Error()
+		state = gridtypes.StateError
 
-	var unchanged ErrUnchanged
-	if errors.As(err, &unchanged) {
-		result.Error = unchanged.Error()
-		result.State = gridtypes.StateUnChanged
-	} else if err != nil {
-		result.Error = err.Error()
-		result.State = gridtypes.StateError
-	} else {
-		result.State = gridtypes.StateOk
+		var resp *response
+		if errors.As(err, &resp) {
+			state = resp.state()
+		}
 	}
 
+	result.State = state
+	result.Error = str
+}
+
+func buildResult(data interface{}, err error) (gridtypes.Result, error) {
+	var result gridtypes.Result
+	setState(&result, err)
+
 	br, err := json.Marshal(data)
 	if err != nil {
 		return result, errors.Wrap(err, "failed to encode result")
 	}
+
 	result.Data = br
 
 	return result, nil
diff --git a/pkg/provision/storage.fs/storage.go b/pkg/provision/storage.fs/storage.go
index b1f74e67..3078b83b 100644
--- a/pkg/provision/storage.fs/storage.go
+++ b/pkg/provision/storage.fs/storage.go
@@ -185,7 +185,7 @@ func (s *Fs) Set(dl gridtypes.Deployment) error {
 	for _, wl := range dl.GetShareables() {
 		// if workload result is not set yet. or if the state is OK
 		// it means the workload still need to be treated as shared object
-		if wl.Result.IsNil() || wl.Result.State == gridtypes.StateOk {
+		if wl.Result.IsNil() || wl.Result.State.IsOkay() {
 			// workload with no results, so we should keep the link
 			if _, ok := this[wl.Name]; ok {
 				// avoid unlinking
@@ -349,7 +349,7 @@ func (s *Fs) Capacity() (cap gridtypes.Capacity, err error) {
 			}
 
 			for _, wl := range deployment.Workloads {
-				if wl.Result.State != gridtypes.StateOk {
+				if !wl.Result.State.IsOkay() {
 					continue
 				}
 
diff --git a/pkg/provision/storage/storage.go b/pkg/provision/storage/storage.go
index 2407234b..3dbeb32d 100644
--- a/pkg/provision/storage/storage.go
+++ b/pkg/provision/storage/storage.go
@@ -668,7 +668,7 @@ func (b *BoltStorage) Capacity() (cap gridtypes.Capacity, active []gridtypes.Dep
 
 			isActive := false
 			for _, wl := range deployment.Workloads {
-				if wl.Result.State != gridtypes.StateOk {
+				if !wl.Result.State.IsOkay() {
 					continue
 				}
 
diff --git a/pkg/storage/disk.go b/pkg/storage/disk.go
index 6ef3618a..b527b966 100644
--- a/pkg/storage/disk.go
+++ b/pkg/storage/disk.go
@@ -191,7 +191,7 @@ func (s *Module) DiskCreate(name string, size gridtypes.Unit) (disk pkg.VDisk, e
 		return disk, err
 	}
 
-	if err := file.Truncate(int64(size)); err != nil {
+	if err = syscall.Fallocate(int(file.Fd()), 0, 0, int64(size)); err != nil {
 		return disk, errors.Wrap(err, "failed to truncate disk to size")
 	}
 
@@ -212,7 +212,7 @@ func (s *Module) DiskResize(name string, size gridtypes.Unit) (disk pkg.VDisk, e
 
 	defer file.Close()
 
-	if err := file.Truncate(int64(size)); err != nil {
+	if err = syscall.Fallocate(int(file.Fd()), 0, 0, int64(size)); err != nil {
 		return disk, errors.Wrap(err, "failed to truncate disk to size")
 	}
 
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index bbf666c5..0ac53e60 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -27,7 +27,7 @@ import (
 
 const (
 	// SSDOverProvisionFactor over provision factor for SSDs
-	SSDOverProvisionFactor = 2
+	SSDOverProvisionFactor = 1
 	// CacheTarget is the path where the cache disk is mounted
 	CacheTarget = "/var/cache"
 	// cacheLabel is the name of the cache
diff --git a/pkg/stubs/events_stub.go b/pkg/stubs/events_stub.go
index 52e06465..0c12f49c 100644
--- a/pkg/stubs/events_stub.go
+++ b/pkg/stubs/events_stub.go
@@ -47,6 +47,30 @@ func (s *EventsStub) ContractCancelledEvent(ctx context.Context) (<-chan pkg.Con
 	return ch, nil
 }
 
+func (s *EventsStub) ContractLockedEvent(ctx context.Context) (<-chan pkg.ContractLockedEvent, error) {
+	ch := make(chan pkg.ContractLockedEvent)
+	recv, err := s.client.Stream(ctx, s.module, s.object, "ContractLockedEvent")
+	if err != nil {
+		return nil, err
+	}
+	go func() {
+		defer close(ch)
+		for event := range recv {
+			var obj pkg.ContractLockedEvent
+			if err := event.Unmarshal(&obj); err != nil {
+				panic(err)
+			}
+			select {
+			case <-ctx.Done():
+				return
+			case ch <- obj:
+			default:
+			}
+		}
+	}()
+	return ch, nil
+}
+
 func (s *EventsStub) PublicConfigEvent(ctx context.Context) (<-chan pkg.PublicConfigEvent, error) {
 	ch := make(chan pkg.PublicConfigEvent)
 	recv, err := s.client.Stream(ctx, s.module, s.object, "PublicConfigEvent")
diff --git a/pkg/stubs/vmd_stub.go b/pkg/stubs/vmd_stub.go
index 10e65b20..1431ea90 100644
--- a/pkg/stubs/vmd_stub.go
+++ b/pkg/stubs/vmd_stub.go
@@ -80,6 +80,19 @@ func (s *VMModuleStub) List(ctx context.Context) (ret0 []string, ret1 error) {
 	return
 }
 
+func (s *VMModuleStub) Lock(ctx context.Context, arg0 string, arg1 bool) (ret0 error) {
+	args := []interface{}{arg0, arg1}
+	result, err := s.client.RequestContext(ctx, s.module, s.object, "Lock", args...)
+	if err != nil {
+		panic(err)
+	}
+	ret0 = new(zbus.RemoteError)
+	if err := result.Unmarshal(0, &ret0); err != nil {
+		panic(err)
+	}
+	return
+}
+
 func (s *VMModuleStub) Logs(ctx context.Context, arg0 string) (ret0 string, ret1 error) {
 	args := []interface{}{arg0}
 	result, err := s.client.RequestContext(ctx, s.module, s.object, "Logs", args...)
diff --git a/pkg/vm.go b/pkg/vm.go
index f8496bbb..fe67185a 100644
--- a/pkg/vm.go
+++ b/pkg/vm.go
@@ -259,7 +259,8 @@ type VMModule interface {
 	Logs(name string) (string, error)
 	List() ([]string, error)
 	Metrics() (MachineMetrics, error)
-
+	// Lock set lock on VM (pause,resume)
+	Lock(name string, lock bool) error
 	// VM Log streams
 
 	// StreamCreate creates a stream for vm `name`
diff --git a/pkg/vm/client.go b/pkg/vm/client.go
index b4c29fff..f41896cc 100644
--- a/pkg/vm/client.go
+++ b/pkg/vm/client.go
@@ -49,6 +49,42 @@ func (c *Client) Shutdown(ctx context.Context) error {
 	return nil
 }
 
+func (c *Client) Pause(ctx context.Context) error {
+	request, err := http.NewRequestWithContext(ctx, http.MethodPut, "http://unix/api/v1/vm.pause", nil)
+	if err != nil {
+		return err
+	}
+	response, err := c.client.Do(request)
+	if err != nil {
+		return errors.Wrap(err, "error calling machine pause")
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != http.StatusNoContent {
+		return fmt.Errorf("got unexpected http code '%s' on machine pause", response.Status)
+	}
+
+	return nil
+}
+
+func (c *Client) Resume(ctx context.Context) error {
+	request, err := http.NewRequestWithContext(ctx, http.MethodPut, "http://unix/api/v1/vm.resume", nil)
+	if err != nil {
+		return err
+	}
+	response, err := c.client.Do(request)
+	if err != nil {
+		return errors.Wrap(err, "error calling machine pause")
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != http.StatusNoContent {
+		return fmt.Errorf("got unexpected http code '%s' on machine resume", response.Status)
+	}
+
+	return nil
+}
+
 // Inspect return information about the vm
 func (c *Client) Inspect(ctx context.Context) (CPU, MemMib, error) {
 	request, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://unix/api/v1/vm.info", nil)
diff --git a/pkg/vm/manager.go b/pkg/vm/manager.go
index 8e5f8842..1fb332c8 100644
--- a/pkg/vm/manager.go
+++ b/pkg/vm/manager.go
@@ -609,3 +609,19 @@ func (m *Module) Delete(name string) error {
 
 	return nil
 }
+
+func (m *Module) Lock(name string, lock bool) error {
+	// todo: should we do locking here?
+	if !m.Exists(name) {
+		return fmt.Errorf("machine '%s' does not exist", name)
+	}
+
+	ctx := context.Background()
+
+	client := NewClient(m.socketPath(name))
+	if lock {
+		return client.Pause(ctx)
+	} else {
+		return client.Resume(ctx)
+	}
+}
diff --git a/pkg/zdb/admin.go b/pkg/zdb/admin.go
index 4f267b9d..3832bf27 100644
--- a/pkg/zdb/admin.go
+++ b/pkg/zdb/admin.go
@@ -103,6 +103,24 @@ func (c *clientImpl) NamespaceSetMode(name, mode string) error {
 	return nil
 }
 
+// NamespaceSetMode locks the namespace by a password, use * password to clear it
+func (c *clientImpl) NamespaceSetLock(name string, lock bool) error {
+	con := c.pool.Get()
+	defer con.Close()
+	l := 0
+	if lock {
+		l = 1
+	}
+	ok, err := redis.String(con.Do("NSSET", name, "freeze", l))
+	if err != nil {
+		return err
+	}
+	if ok != "OK" {
+		return fmt.Errorf(ok)
+	}
+	return nil
+}
+
 // NamespaceSetPublic changes the public flag, a public namespace can be read-only if a password is set
 func (c *clientImpl) NamespaceSetPublic(name string, public bool) error {
 	con := c.pool.Get()
diff --git a/pkg/zdb/zdb.go b/pkg/zdb/zdb.go
index bfd1a5aa..eab51795 100644
--- a/pkg/zdb/zdb.go
+++ b/pkg/zdb/zdb.go
@@ -33,6 +33,7 @@ type Client interface {
 	NamespaceSetPassword(name, password string) error
 	NamespaceSetMode(name, mode string) error
 	NamespaceSetPublic(name string, public bool) error
+	NamespaceSetLock(name string, lock bool) error
 	DBSize() (uint64, error)
 }
 


Test patch:
diff --git a/pkg/primitives/zdb_test.go b/pkg/primitives/zdb_test.go
deleted file mode 100644
index cb95bea5..00000000
--- a/pkg/primitives/zdb_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package primitives
-
-// import (
-// 	"net"
-// 	"testing"
-// )
-
-// func Test_isYgg(t *testing.T) {
-// 	type args struct {
-// 		ip net.IP
-// 	}
-// 	tests := []struct {
-// 		name string
-// 		args args
-// 		want bool
-// 	}{
-// 		{
-// 			name: "301:8d90:378f:a93:6364:f8c8:1c6b:865d",
-// 			args: args{
-// 				ip: net.ParseIP("301:8d90:378f:a93:6364:f8c8:1c6b:865d"),
-// 			},
-// 			want: true,
-// 		},
-// 		{
-// 			name: "2a02:2788:864:1314:9eb6:d0ff:fe97:764b",
-// 			args: args{
-// 				ip: net.ParseIP("2a02:2788:864:1314:9eb6:d0ff:fe97:764b"),
-// 			},
-// 			want: false,
-// 		},
-// 	}
-// 	for _, tt := range tests {
-// 		t.Run(tt.name, func(t *testing.T) {
-// 			if got := isYgg(tt.args.ip); got != tt.want {
-// 				t.Errorf("isYgg() = %v, want %v", got, tt.want)
-// 			}
-// 		})
-// 	}
-// }
diff --git a/pkg/provision/provisioner_test.go b/pkg/provision/provisioner_test.go
new file mode 100644
index 00000000..200a845f
--- /dev/null
+++ b/pkg/provision/provisioner_test.go
@@ -0,0 +1,152 @@
+package provision
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+	"github.com/threefoldtech/zos/pkg/gridtypes"
+)
+
+func TestBuildResult(t *testing.T) {
+	type Case struct {
+		in  error
+		out gridtypes.Result
+	}
+	cases := []Case{
+		{
+			in: nil,
+			out: gridtypes.Result{
+				State: gridtypes.StateOk,
+				Error: "",
+			},
+		},
+		{
+			in: Ok(),
+			out: gridtypes.Result{
+				State: gridtypes.StateOk,
+				Error: "",
+			},
+		},
+		{
+			in: fmt.Errorf("something went wrong"),
+			out: gridtypes.Result{
+				State: gridtypes.StateError,
+				Error: "something went wrong",
+			},
+		},
+		{
+			in: UnChanged(fmt.Errorf("failed to update")),
+			out: gridtypes.Result{
+				State: gridtypes.StateUnChanged,
+				Error: "failed to update",
+			},
+		},
+		{
+			in: errors.Wrap(UnChanged(fmt.Errorf("failed to update")), "wrapped"),
+			out: gridtypes.Result{
+				State: gridtypes.StateUnChanged,
+				Error: "wrapped: failed to update",
+			},
+		},
+		{
+			in: Paused(),
+			out: gridtypes.Result{
+				State: gridtypes.StatePaused,
+				Error: "paused",
+			},
+		},
+		{
+			in: errors.Wrap(Paused(), "wrapped for some reason"),
+			out: gridtypes.Result{
+				State: gridtypes.StatePaused,
+				Error: "wrapped for some reason: paused",
+			},
+		},
+	}
+
+	for _, c := range cases {
+		t.Run("", func(t *testing.T) {
+			result, err := buildResult(nil, c.in)
+			require.NoError(t, err)
+
+			require.Equal(t, c.out.State, result.State)
+			require.Equal(t, c.out.Error, result.Error)
+		})
+	}
+}
+
+var (
+	testWorkloadType gridtypes.WorkloadType = "test"
+)
+
+type testManagerFull struct {
+	mock.Mock
+}
+
+func (t *testManagerFull) Provision(ctx context.Context, wl *gridtypes.WorkloadWithID) (interface{}, error) {
+	args := t.Called(ctx, wl)
+	return args.Get(0), args.Error(1)
+}
+
+func (t *testManagerFull) Deprovision(ctx context.Context, wl *gridtypes.WorkloadWithID) error {
+	args := t.Called(ctx, wl)
+	return args.Error(0)
+}
+
+func TestProvision(t *testing.T) {
+	require := require.New(t)
+	var mgr testManagerFull
+	provisioner := NewMapProvisioner(map[gridtypes.WorkloadType]Manager{
+		testWorkloadType: &mgr,
+	})
+
+	ctx := context.Background()
+	wl := gridtypes.WorkloadWithID{
+		Workload: &gridtypes.Workload{
+			Type: testWorkloadType,
+		},
+	}
+
+	mgr.On("Provision", mock.Anything, &wl).Return(123, nil)
+	result, err := provisioner.Provision(ctx, &wl)
+
+	require.NoError(err)
+	require.Equal(gridtypes.StateOk, result.State)
+	require.Equal(json.RawMessage("123"), result.Data)
+
+	mgr.ExpectedCalls = nil
+	mgr.On("Provision", mock.Anything, &wl).Return(nil, fmt.Errorf("failed to run"))
+	result, err = provisioner.Provision(ctx, &wl)
+
+	require.NoError(err)
+	require.Equal(gridtypes.StateError, result.State)
+	require.Equal("failed to run", result.Error)
+
+	mgr.ExpectedCalls = nil
+	mgr.On("Pause", mock.Anything, &wl).Return(nil, nil)
+	result, err = provisioner.Pause(ctx, &wl)
+
+	require.Errorf(err, "can only pause workloads in ok state")
+
+	mgr.ExpectedCalls = nil
+	wl = gridtypes.WorkloadWithID{
+		Workload: &gridtypes.Workload{
+			Type: testWorkloadType,
+			Result: gridtypes.Result{
+				State: gridtypes.StateOk,
+			},
+		},
+	}
+
+	// not here paused will set the right state even if manager
+	// does not support this state.
+	mgr.On("Pause", mock.Anything, &wl).Return(nil, nil)
+	result, err = provisioner.Pause(ctx, &wl)
+	require.NoError(err)
+	require.Equal(gridtypes.StatePaused, result.State)
+}
diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go
index 3a2b075f..18ff9a0b 100644
--- a/pkg/storage/storage_test.go
+++ b/pkg/storage/storage_test.go
@@ -392,10 +392,10 @@ func TestVDiskFindCandidatesOverProvision(t *testing.T) {
 
 	pool1.On("Volumes").Return([]filesystem.Volume{sub}, nil)
 
-	_, err := mod.diskFindCandidate(8000)
+	_, err := mod.diskFindCandidate(4000)
 	require.NoError(err)
 
-	_, err = mod.diskFindCandidate(10000)
+	_, err = mod.diskFindCandidate(5000)
 	require.Error(err)
 
 }