Repository: threefoldtech/zos
Pull request #1053 (2020-11-10 16:40:44)

identityd drop dependency on 0-fs
- Modify the upgrade process to use 0-fs library
- The current code uses not release 0-fs code (hence draft) 
  - We need to merge https://github.com/threefoldtech/0-fs/pull/44 first and create a release before we can merge this


Patch:
diff --git a/cmds/Makefile b/cmds/Makefile
index 52f1eadf..20f094ce 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -8,10 +8,13 @@ ldflags = '-w -s -X $(version).Branch=$(branch) -X $(version).Revision=$(revisio
 all: $(shell ls -d */)
 	strip $(OUT)/*
 
-.PHONY: output clean
+.PHONY: output clean identityd
 
 output:
 	mkdir -p $(OUT)
 
+identityd:
+	cd identityd && GOOS=linux go build -ldflags $(ldflags) -o $(OUT)/identityd
+
 %:	%/*.go output
 	cd $(shell dirname $<) && CGO_ENABLED=0 GOOS=linux go build -ldflags $(ldflags) -o $(OUT)/$(shell basename `dirname $<`)
diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go
index 7a55b123..9813f22f 100644
--- a/cmds/identityd/main.go
+++ b/cmds/identityd/main.go
@@ -17,7 +17,6 @@ import (
 	"github.com/threefoldtech/tfexplorer/client"
 	"github.com/threefoldtech/tfexplorer/models/generated/directory"
 	"github.com/threefoldtech/zos/pkg/app"
-	"github.com/threefoldtech/zos/pkg/flist"
 	"github.com/threefoldtech/zos/pkg/geoip"
 	"github.com/threefoldtech/zos/pkg/network"
 	"github.com/threefoldtech/zos/pkg/stubs"
@@ -40,7 +39,6 @@ import (
 
 const (
 	redisSocket = "unix:///var/run/redis.sock"
-	zinitSocket = "/var/run/zinit.sock"
 )
 
 const (
@@ -195,22 +193,12 @@ func main() {
 		}
 	}()
 
-	zinit, err := zinit.New(zinitSocket)
+	upgrader, err := upgrade.NewUpgrader(root, upgrade.NoSelfUpgrade(debug))
 	if err != nil {
-		log.Fatal().Err(err).Msg("failed to connect to zinit")
+		log.Fatal().Err(err).Msg("failed to initialize upgrader")
 	}
 
-	// with volume allocation is set to nil this flister can be
-	// only used for RO mounts. Otherwise it will panic
-	flister := flist.New(root, nil)
-
-	upgrader := upgrade.Upgrader{
-		FLister:      flister,
-		Zinit:        zinit,
-		NoSelfUpdate: debug,
-	}
-
-	installBinaries(&boot, &upgrader)
+	installBinaries(&boot, upgrader)
 
 	utils.OnDone(ctx, func(_ error) {
 		log.Info().Msg("received a termination signal")
@@ -228,7 +216,7 @@ func main() {
 	// 4. Start watcher for new version
 	log.Info().Msg("start upgrade daemon")
 
-	upgradeLoop(ctx, &boot, &upgrader, debug, monitor, register)
+	upgradeLoop(ctx, &boot, upgrader, debug, monitor, register)
 }
 
 // allow reinstall if receive signal USR1
@@ -366,6 +354,9 @@ func upgradeLoop(
 					return backoff.Permanent(err)
 				}
 
+				if err != nil {
+					log.Error().Err(err).Msg("update failure. retrying")
+				}
 				return err
 			}, exp)
 
diff --git a/go.mod b/go.mod
index 49280fde..12e895d3 100644
--- a/go.mod
+++ b/go.mod
@@ -47,9 +47,11 @@ require (
 	github.com/gomodule/redigo v2.0.0+incompatible
 	github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
 	github.com/google/uuid v1.1.1
+	github.com/hanwen/go-fuse v1.0.0 // indirect
 	github.com/imdario/mergo v0.3.9 // indirect
 	github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6
 	github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077 // indirect
+	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
 	github.com/opencontainers/image-spec v1.0.1 // indirect
 	github.com/opencontainers/runc v0.1.1 // indirect
 	github.com/opencontainers/runtime-spec v1.0.1
@@ -63,6 +65,7 @@ require (
 	github.com/stretchr/testify v1.6.1
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
 	github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae
+	github.com/threefoldtech/0-fs v1.3.1-0.20201111155454-6f328c14703a
 	github.com/threefoldtech/tfexplorer v0.4.1-0.20201014110255-653c7314b944
 	github.com/threefoldtech/zbus v0.1.4
 	github.com/urfave/cli v1.22.4
@@ -71,18 +74,20 @@ require (
 	github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
 	github.com/whs/nacl-sealed-box v0.0.0-20180930164530-92b9ba845d8d
 	github.com/xdg/stringprep v1.0.0 // indirect
+	github.com/xxtea/xxtea-go v0.0.0-20170828040851-35c4b17eecf6 // indirect
 	github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20200526002434-ed3bf5ef0736
 	go.etcd.io/bbolt v1.3.4 // indirect
-	golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4
-	golang.org/x/net v0.0.0-20200301022130-244492dfa37a
-	golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
+	golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
+	golang.org/x/net v0.0.0-20200707034311-ab3426394381
+	golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191219145116-fa6499c8e75f
 	google.golang.org/appengine v1.6.5 // indirect
 	google.golang.org/grpc v1.29.1 // indirect
-	gopkg.in/yaml.v2 v2.2.7
+	gopkg.in/yaml.v2 v2.3.0
 	gotest.tools v2.2.0+incompatible
 	gotest.tools/v3 v3.0.2 // indirect
+	zombiezen.com/go/capnproto2 v2.18.0+incompatible // indirect
 )
 
 replace github.com/docker/distribution v2.7.1+incompatible => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
diff --git a/go.sum b/go.sum
index 70901146..f9d89b76 100644
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,7 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+q
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
 github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
 github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
@@ -41,6 +42,7 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C
 github.com/alexflint/go-filemutex v0.0.0-20171028004239-d358565f3c3f h1:tbgFqBK8r77y+mT2RKkQ8ukhk/uvPtPZvr3a3166YNw=
 github.com/alexflint/go-filemutex v0.0.0-20171028004239-d358565f3c3f/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
@@ -71,6 +73,7 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3 h1:i8+1fuPLjSgAYXUyBlH
 github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
 github.com/containerd/cgroups v0.0.0-20190911145653-fc51bcbe4714/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989 h1:ziD9Q8QdO0vybP3fYg5MQ+PqDAb61jEbdCsL2f5xXK4=
 github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8=
@@ -154,8 +157,11 @@ github.com/firecracker-microvm/firecracker-go-sdk v0.19.1-0.20200110212531-741fc
 github.com/firecracker-microvm/firecracker-go-sdk v0.19.1-0.20200110212531-741fc8cb0f2e/go.mod h1:kW0gxvPpPvMukUxxTO9DrpSlScrtrTDGY3VgjAj/Qwc=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
 github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
 github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
+github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 h1:gmtGRvSexPU4B1T/yYo0sLOKzER1YT+b4kPxPpm0Ty4=
 github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA=
 github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -319,6 +325,7 @@ github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buO
 github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -327,6 +334,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gordonklaus/ineffassign v0.0.0-20200809085317-e36bfde3bb78/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -336,12 +344,19 @@ github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
 github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
 github.com/graph-gophers/graphql-go v0.0.0-20190225005345-3e8838d4614c/go.mod h1:uJhtPXrcJLqyi0H5IuMFh+fgW+8cMMakK3Txrbk/WJE=
 github.com/guregu/null v2.1.3-0.20151024101046-79c5bd36b615+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
+github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
+github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
+github.com/hanwen/go-fuse/v2 v2.0.3 h1:kpV28BKeSyVgZREItBLnaVBvOEwv2PuhNdKetwnvNHo=
+github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
 github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@@ -375,6 +390,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -398,6 +414,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.0.0-20150520163712-e373e137fafd/go.mod h1:sjUstKUATFIcff4qlB53Kml0wQPtJVc/3fWrmuUmcfA=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/lann/builder v0.0.0-20140829050551-c603884a2c1f/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
@@ -426,7 +443,10 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
+github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
+github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mdlayher/genetlink v0.0.0-20191205172946-651acf4b47ef h1:VOblll+3pOfnsJfEjrEX3TeKeF/gKkXOK20KMR7II+8=
 github.com/mdlayher/genetlink v0.0.0-20191205172946-651acf4b47ef/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
@@ -463,6 +483,8 @@ github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGV
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -485,6 +507,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -513,6 +536,7 @@ github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLk
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI=
 github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
 github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -538,6 +562,7 @@ github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca h1:oR/RycYTFTVXzND5r
 github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
 github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
 github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
@@ -597,6 +622,11 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtse
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM=
 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs=
+github.com/threefoldtech/0-fs v1.3.0 h1:TOjfMNhLtQSDDUssTE6SZ/Zv2KfZNfWcfW7xyeviMVs=
+github.com/threefoldtech/0-fs v1.3.1-0.20201111155454-6f328c14703a h1:Lf8jNyiWM3eNECGMOQSZvkDWWMR2AaO9LfUFV+ddp5c=
+github.com/threefoldtech/0-fs v1.3.1-0.20201111155454-6f328c14703a/go.mod h1:OPPZiE/GthPR2IepjKSc8wa+t/7wl3dtHQyEdUcftZI=
+github.com/threefoldtech/0-fs v2.0.1+incompatible h1:4c3jorr06q8vTstdevhW2d0Qa/xaGMkhvFYizuZkhcA=
+github.com/threefoldtech/0-fs v2.0.1+incompatible/go.mod h1:Ftk02AVzIK1rbFhCgpJr/cY11WWNgIPQytO2cA+jemE=
 github.com/threefoldtech/tfexplorer v0.2.5/go.mod h1:TugylEDgMNKk4ZIzee9PpVmIcLNLaRLdiXuhHTt/AR0=
 github.com/threefoldtech/tfexplorer v0.3.2-0.20200824142536-b4319e0a006d h1:/4U3Td7mPrizg0xk5k09KBYQRCG98r6K2aCLiXHSv1E=
 github.com/threefoldtech/tfexplorer v0.3.2-0.20200824142536-b4319e0a006d/go.mod h1:oFtPBaRLW4c6YuEfTrWYzAGw+wb6I4+og133zjDOYP0=
@@ -629,6 +659,7 @@ github.com/threefoldtech/zos v0.2.4/go.mod h1:B1SeNF7YWpDEUFX4dgAXImHO49HB5zDuD+
 github.com/threefoldtech/zos v0.4.0-rc9-b.0.20200918140104-b46553b0c680/go.mod h1:Fyh+Cwa2GlUwlDpu4SlZttW//fKgWc1RePtKSuJAfdE=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
 github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8=
 github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
@@ -667,6 +698,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c h1:XZWnr3b
 github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce h1:cVSRGH8cOveJNwFEEZLXtB+XMnRqKLjUP6V/ZFYQCXI=
 github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xxtea/xxtea-go v0.0.0-20170828040851-35c4b17eecf6 h1:S+0oS/OPAe0kdSpQ7GAnCmpcDL7Jh2iJMjZTV6mYbPo=
+github.com/xxtea/xxtea-go v0.0.0-20170828040851-35c4b17eecf6/go.mod h1:2uvuCBt0VXxijrX5ieiAeeNT2+2MIsrs1DI9iXz7OOQ=
 github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb h1:06WAhQa+mYv7BiOk13B/ywyTlkoE/S7uu6TBKU6FHnE=
 github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
 github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I=
@@ -678,6 +711,7 @@ github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce h1:888GrqRxabUce7lj4Oa
 github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
 github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
 github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/zaibon/httpsig v0.0.0-20200401133919-ea9cb57b0946 h1:s12kADbuUVEJkYxqUojKvFR5JUpib9PCzARL1YR2ZDU=
 github.com/zaibon/httpsig v0.0.0-20200401133919-ea9cb57b0946/go.mod h1:T2e9wAAQHKLB08ONx+0xxxrlAfUnRbwLOpO0WodNbQ4=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
@@ -698,21 +732,31 @@ golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
 golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -734,8 +778,13 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -746,6 +795,8 @@ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -783,6 +834,9 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 h1:qDJKu1y/1SjhWac4BQZjLljqvqiWUhjmDMnonmVGDAU=
+golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3-0.20191230102452-929e72ca90de h1:aYKJLPSrddB2N7/6OKyFqJ337SXpo61bBuvO5p1+7iY=
@@ -798,6 +852,7 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190228203856-589c23e65e65/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -814,7 +869,12 @@ golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4=
 golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200823205832-c024452afbcd/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
@@ -871,6 +931,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
@@ -881,3 +943,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
+zombiezen.com/go/capnproto2 v2.18.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
diff --git a/pkg/crypto/encryption.go b/pkg/crypto/encryption.go
index 1e9e6e30..01adf68b 100644
--- a/pkg/crypto/encryption.go
+++ b/pkg/crypto/encryption.go
@@ -23,6 +23,10 @@ func Decrypt(msg []byte, sk ed25519.PrivateKey) ([]byte, error) {
 	curvePriv := PrivateKeyToCurve25519(sk)
 	curvePub := PublicKeyToCurve25519(sk.Public().(ed25519.PublicKey))
 
+	if len(msg) < 48 {
+		return nil, fmt.Errorf("invalid cipher text too short")
+	}
+
 	return box.Open(msg, &curvePub, &curvePriv)
 }
 
diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go
index 01eb7152..cc40fae2 100644
--- a/pkg/provision/engine.go
+++ b/pkg/provision/engine.go
@@ -235,25 +235,31 @@ func (e *Engine) provision(ctx context.Context, r *Reservation) error {
 		return errors.Wrapf(err, "failed to build result object for reservation: %s", result.ID)
 	}
 
-	// we make sure we store the reservation in cache first before
-	// returning the reply back to the grid, this is to make sure
-	// if the reply failed for any reason, the node still doesn't
-	// try to redeploy that reservation.
-	r.ID = realID
-	r.Result = *result
-	if err := e.cache.Add(r); err != nil {
-		return errors.Wrapf(err, "failed to cache reservation %s locally", r.ID)
-	}
-
+	// send response to explorer
 	if err := e.reply(ctx, result); err != nil {
 		log.Error().Err(err).Msg("failed to send result to BCDB")
 	}
 
-	// we skip the counting.
+	// if we fail to decomission the reservation then must be marked
+	// as deleted so it's never tried again. we also skip caching
+	// the reservation object. this is similar to what decomission does
+	// since on a decomission we also clear up the cache.
 	if provisionError != nil {
+		// we need to mark the reservation as deleted as well
+		if err := e.feedback.Deleted(e.nodeID, realID); err != nil {
+			log.Error().Err(err).Msg("failed to mark failed reservation as deleted")
+		}
+
 		return provisionError
 	}
 
+	// we only cache successful reservations
+	r.ID = realID
+	r.Result = *result
+	if err := e.cache.Add(r); err != nil {
+		return errors.Wrapf(err, "failed to cache reservation %s locally", r.ID)
+	}
+
 	// If an update occurs on the network we don't increment the counter
 	if r.Type == "network_resource" {
 		nr := pkg.NetResource{}
@@ -304,6 +310,18 @@ func (e *Engine) decommission(ctx context.Context, r *Reservation) error {
 		r.ID = r.Reference
 	}
 
+	if r.Result.State == StateError {
+		// this reservation already failed to deploy
+		// this code here shouldn't be executing because if
+		// the reservation has error-ed, it means is should
+		// not be in cache.
+		// BUT
+		// that was not always the case, so instead we
+		// will just return. here
+		log.Warn().Str("id", realID).Msg("skipping reservation because it is not provisioned")
+		return nil
+	}
+
 	err = fn(ctx, r)
 	if err != nil {
 		return errors.Wrap(err, "decommissioning of reservation failed")
@@ -412,18 +430,24 @@ func (e *Engine) updateStats() error {
 	wl := e.statser.CurrentWorkloads()
 	r := e.statser.CurrentUnits()
 
-	storaged := stubs.NewStorageModuleStub(e.zbusCl)
+	if e.zbusCl != nil {
+		// TODO: this is a very specific zos code that should not be
+		// here. this is a quick fix for the tfgateways
+		// but should be implemented cleanely after
+		storaged := stubs.NewStorageModuleStub(e.zbusCl)
 
-	cache, err := storaged.GetCacheFS()
-	if err != nil {
-		return err
-	}
+		cache, err := storaged.GetCacheFS()
+		if err != nil {
+			return err
+		}
+
+		switch cache.DiskType {
+		case pkg.SSDDevice:
+			r.Sru += float64(cache.Usage.Size / gib)
+		case pkg.HDDDevice:
+			r.Hru += float64(cache.Usage.Size / gib)
+		}
 
-	switch cache.DiskType {
-	case pkg.SSDDevice:
-		r.Sru += float64(cache.Usage.Size / gib)
-	case pkg.HDDDevice:
-		r.Hru += float64(cache.Usage.Size / gib)
 	}
 
 	log.Info().
diff --git a/pkg/provision/primitives/counters.go b/pkg/provision/primitives/counters.go
index ec358786..52cb9c10 100644
--- a/pkg/provision/primitives/counters.go
+++ b/pkg/provision/primitives/counters.go
@@ -81,11 +81,12 @@ func (c *Counters) CurrentWorkloads() directory.WorkloadAmount {
 
 // CurrentUnits return the number of each resource units reserved on the system
 func (c *Counters) CurrentUnits() directory.ResourceAmount {
+	gib := float64(gib)
 	return directory.ResourceAmount{
 		Cru: c.CRU.Current(),
-		Mru: float64(c.MRU.Current() / gib),
-		Hru: float64(c.HRU.Current() / gib),
-		Sru: float64(c.SRU.Current() / gib),
+		Mru: float64(c.MRU.Current()) / gib,
+		Hru: float64(c.HRU.Current()) / gib,
+		Sru: float64(c.SRU.Current()) / gib,
 	}
 }
 
diff --git a/pkg/storage.go b/pkg/storage.go
index fc187788..4ffed5a3 100644
--- a/pkg/storage.go
+++ b/pkg/storage.go
@@ -165,9 +165,6 @@ type VolumeAllocater interface {
 
 	// GetCacheFS return the special filesystem used by 0-OS to store internal state and flist cache
 	GetCacheFS() (Filesystem, error)
-
-	// GetVdiskFS return the filesystem used to store the vdisk file for the VM module
-	GetVdiskFS() (Filesystem, error)
 }
 
 // VDisk info returned by a call to inspect
diff --git a/pkg/storage/disk.go b/pkg/storage/disk.go
index a7b97325..8e84e7ad 100644
--- a/pkg/storage/disk.go
+++ b/pkg/storage/disk.go
@@ -19,33 +19,50 @@ const (
 )
 
 type vdiskModule struct {
-	path string
+	module *Module
 }
 
 // NewVDiskModule creates a new disk allocator
-func NewVDiskModule(v pkg.VolumeAllocater) (pkg.VDiskModule, error) {
-	fs, err := v.Path(vdiskVolumeName)
-	if errors.Is(err, os.ErrNotExist) {
-		fs, err = v.CreateFilesystem(vdiskVolumeName, 0, pkg.SSDDevice)
-	}
+func NewVDiskModule(module *Module) (pkg.VDiskModule, error) {
+	return &vdiskModule{module: module}, nil
+}
 
+func (d *vdiskModule) findDisk(id string) (string, error) {
+	pools, err := d.module.VDiskPools()
 	if err != nil {
-		return nil, err
+		return "", errors.Wrapf(err, "failed to find disk with id '%s'", id)
+	}
+
+	for _, pool := range pools {
+		path, err := d.safePath(pool, id)
+		if err != nil {
+			return "", err
+		}
+
+		if _, err := os.Stat(path); err == nil {
+			// file exists
+			return path, nil
+		}
 	}
 
-	return &vdiskModule{path: filepath.Clean(fs.Path)}, nil
+	return "", os.ErrNotExist
 }
 
 // AllocateDisk with given size, return path to virtual disk (size in MB)
 func (d *vdiskModule) Allocate(id string, size int64) (string, error) {
-	path, err := d.safePath(id)
+	path, err := d.findDisk(id)
+	if err == nil {
+		return path, errors.Wrapf(os.ErrExist, "disk with id '%s' already exists", id)
+	}
+
+	base, err := d.module.VDiskFindCandidate(uint64(size))
 	if err != nil {
-		return "", err
+		return "", errors.Wrapf(err, "failed to find a candidate to host vdisk of size '%d'", size)
 	}
 
-	if _, err := os.Stat(path); err == nil {
-		// file exists
-		return path, errors.Wrapf(os.ErrExist, "disk with id '%s' already exists", id)
+	path, err = d.safePath(base, id)
+	if err != nil {
+		return "", err
 	}
 
 	file, err := os.Create(path)
@@ -58,13 +75,13 @@ func (d *vdiskModule) Allocate(id string, size int64) (string, error) {
 	return path, syscall.Fallocate(int(file.Fd()), 0, 0, size*mib)
 }
 
-func (d *vdiskModule) safePath(id string) (string, error) {
-	path := filepath.Join(d.path, id)
+func (d *vdiskModule) safePath(base, id string) (string, error) {
+	path := filepath.Join(base, id)
 	// this to avoid passing an `injection` id like '../name'
 	// and end up deleting a file on the system. so only delete
 	// allocated disks
 	location := filepath.Dir(path)
-	if filepath.Clean(location) != d.path {
+	if filepath.Clean(location) != base {
 		return "", fmt.Errorf("invalid disk id: '%s'", id)
 	}
 
@@ -73,8 +90,10 @@ func (d *vdiskModule) safePath(id string) (string, error) {
 
 // DeallocateVDisk removes a virtual disk
 func (d *vdiskModule) Deallocate(id string) error {
-	path, err := d.safePath(id)
-	if err != nil {
+	path, err := d.findDisk(id)
+	if os.IsNotExist(err) {
+		return nil
+	} else if err != nil {
 		return err
 	}
 
@@ -87,24 +106,16 @@ func (d *vdiskModule) Deallocate(id string) error {
 
 // DeallocateVDisk removes a virtual disk
 func (d *vdiskModule) Exists(id string) bool {
-	path, err := d.safePath(id)
-
-	if err != nil {
-		// invalid ID
-		return false
-	}
-
-	_, err = os.Stat(path)
+	_, err := d.findDisk(id)
 
 	return err == nil
 }
 
 // Inspect return info about the disk
 func (d *vdiskModule) Inspect(id string) (disk pkg.VDisk, err error) {
-	path, err := d.safePath(id)
+	path, err := d.findDisk(id)
 
 	if err != nil {
-		// invalid ID
 		return disk, err
 	}
 
@@ -119,21 +130,30 @@ func (d *vdiskModule) Inspect(id string) (disk pkg.VDisk, err error) {
 }
 
 func (d *vdiskModule) List() ([]pkg.VDisk, error) {
-	items, err := ioutil.ReadDir(d.path)
+	pools, err := d.module.VDiskPools()
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to list virtual disks")
+		return nil, err
 	}
+	var disks []pkg.VDisk
+	for _, pool := range pools {
+
+		items, err := ioutil.ReadDir(pool)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to list virtual disks")
+		}
+
+		for _, item := range items {
+			if item.IsDir() {
+				continue
+			}
 
-	disks := make([]pkg.VDisk, 0, len(items))
-	for _, item := range items {
-		if item.IsDir() {
-			continue
+			disks = append(disks, pkg.VDisk{
+				Path: filepath.Join(pool, item.Name()),
+				Size: item.Size(),
+			})
 		}
 
-		disks = append(disks, pkg.VDisk{
-			Path: filepath.Join(d.path, item.Name()),
-			Size: item.Size(),
-		})
+		return disks, nil
 	}
 
 	return disks, nil
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index f63ca6d1..81243ca3 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -39,7 +39,8 @@ var (
 	}
 )
 
-type storageModule struct {
+// Module implements functionality for pkg.StorageModule
+type Module struct {
 	pools         []filesystem.Pool
 	brokenPools   []pkg.BrokenPool
 	devices       filesystem.DeviceManager
@@ -49,7 +50,7 @@ type storageModule struct {
 }
 
 // New create a new storage module service
-func New() (pkg.StorageModule, error) {
+func New() (*Module, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 	defer cancel()
 
@@ -60,7 +61,7 @@ func New() (pkg.StorageModule, error) {
 		return nil, err
 	}
 
-	s := &storageModule{
+	s := &Module{
 		pools:         []filesystem.Pool{},
 		brokenPools:   []pkg.BrokenPool{},
 		devices:       m,
@@ -82,7 +83,7 @@ func New() (pkg.StorageModule, error) {
 }
 
 // Total gives the total amount of storage available for a device type
-func (s *storageModule) Total(kind pkg.DeviceType) (uint64, error) {
+func (s *Module) Total(kind pkg.DeviceType) (uint64, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	var total uint64
@@ -124,20 +125,20 @@ func (s *storageModule) Total(kind pkg.DeviceType) (uint64, error) {
 }
 
 // BrokenPools lists the broken storage pools that have been detected
-func (s *storageModule) BrokenPools() []pkg.BrokenPool {
+func (s *Module) BrokenPools() []pkg.BrokenPool {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	return s.brokenPools
 }
 
 // BrokenDevices lists the broken devices that have been detected
-func (s *storageModule) BrokenDevices() []pkg.BrokenDevice {
+func (s *Module) BrokenDevices() []pkg.BrokenDevice {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	return s.brokenDevices
 }
 
-func (s *storageModule) Dump() {
+func (s *Module) dump() {
 	log.Debug().Int("volumes", len(s.pools)).Msg("dumping volumes")
 
 	for _, pool := range s.pools {
@@ -160,7 +161,7 @@ What Initialize will do is the following:
  - Scan free devices, apply the policy.
  - If new pools were created, the pool is going to be mounted automatically
 **/
-func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
+func (s *Module) initialize(policy pkg.StoragePolicy) error {
 	// lock for the entire initialization method, so other code which relies
 	// on this observes this as an atomic operation
 	s.mu.Lock()
@@ -211,7 +212,7 @@ func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
 	sort.Sort(filesystem.ByReadTime(freeDisks))
 
 	// dumping current s.volumes list
-	s.Dump()
+	s.dump()
 
 	log.Info().Msgf("Creating new volumes using policy: %s", policy.Raid)
 
@@ -320,7 +321,7 @@ func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
 	return nil
 }
 
-func (s *storageModule) shutdownUnusedPools() error {
+func (s *Module) shutdownUnusedPools() error {
 	for _, pool := range s.pools {
 		if _, mounted := pool.Mounted(); mounted {
 			volumes, err := pool.Volumes()
@@ -347,7 +348,7 @@ func (s *storageModule) shutdownUnusedPools() error {
 }
 
 // CreateFilesystem with the given size in a storage pool.
-func (s *storageModule) CreateFilesystem(name string, size uint64, poolType pkg.DeviceType) (pkg.Filesystem, error) {
+func (s *Module) CreateFilesystem(name string, size uint64, poolType pkg.DeviceType) (pkg.Filesystem, error) {
 	log.Info().Msgf("Creating new volume with size %d", size)
 	if strings.HasPrefix(name, "zdb") {
 		return pkg.Filesystem{}, fmt.Errorf("invalid volume name. zdb prefix is reserved")
@@ -372,13 +373,14 @@ func (s *storageModule) CreateFilesystem(name string, size uint64, poolType pkg.
 			Size: usage.Size,
 			Used: usage.Used,
 		},
+		DiskType: poolType,
 	}, nil
 }
 
 // ReleaseFilesystem with the given name, this will unmount and then delete
 // the filesystem. After this call, the caller must not perform any more actions
 // on this filesystem
-func (s *storageModule) ReleaseFilesystem(name string) error {
+func (s *Module) ReleaseFilesystem(name string) error {
 	log.Info().Msgf("Deleting volume %v", name)
 
 	for _, pool := range s.pools {
@@ -420,7 +422,7 @@ func (s *storageModule) ReleaseFilesystem(name string) error {
 }
 
 // ListFilesystems return all the filesystem managed by storeaged present on the nodes
-func (s *storageModule) ListFilesystems() ([]pkg.Filesystem, error) {
+func (s *Module) ListFilesystems() ([]pkg.Filesystem, error) {
 	fss := make([]pkg.Filesystem, 0, 10)
 
 	for _, pool := range s.pools {
@@ -464,23 +466,30 @@ func (s *storageModule) ListFilesystems() ([]pkg.Filesystem, error) {
 
 // Path return the path of the mountpoint of the named filesystem
 // if no volume with name exists, an empty path and an error is returned
-func (s *storageModule) Path(name string) (pkg.Filesystem, error) {
+func (s *Module) Path(name string) (pkg.Filesystem, error) {
+	_, fs, err := s.path(name)
+	return fs, err
+}
+
+// Path return the path of the mountpoint of the named filesystem
+// if no volume with name exists, an empty path and an error is returned
+func (s *Module) path(name string) (filesystem.Pool, pkg.Filesystem, error) {
 	for _, pool := range s.pools {
 		if _, mounted := pool.Mounted(); !mounted {
 			continue
 		}
 		filesystems, err := pool.Volumes()
 		if err != nil {
-			return pkg.Filesystem{}, err
+			return nil, pkg.Filesystem{}, err
 		}
 		for _, fs := range filesystems {
 			if fs.Name() == name {
 				usage, err := fs.Usage()
 				if err != nil {
-					return pkg.Filesystem{}, err
+					return nil, pkg.Filesystem{}, err
 				}
 
-				return pkg.Filesystem{
+				return pool, pkg.Filesystem{
 					ID:     fs.ID(),
 					FsType: fs.FsType(),
 					Name:   fs.Name(),
@@ -495,21 +504,75 @@ func (s *storageModule) Path(name string) (pkg.Filesystem, error) {
 		}
 	}
 
-	return pkg.Filesystem{}, errors.Wrapf(os.ErrNotExist, "subvolume '%s' not found", name)
+	return nil, pkg.Filesystem{}, errors.Wrapf(os.ErrNotExist, "subvolume '%s' not found", name)
 }
 
-// GetCacheFS return the special filesystem used by 0-OS to store internal state and flist cache
-func (s *storageModule) GetCacheFS() (pkg.Filesystem, error) {
-	return s.Path(cacheLabel)
+// VDiskFindCandidate find a suitbale location for creating a vdisk of the given size
+func (s *Module) VDiskFindCandidate(size uint64) (path string, err error) {
+	candidates, err := s.findCandidates(size, pkg.SSDDevice)
+	if err != nil {
+		return path, err
+	}
+	// does anyone have a vdisk subvol
+	for _, candidate := range candidates {
+		volumes, err := candidate.Pool.Volumes()
+		if err != nil {
+			log.Error().Str("pool", candidate.Pool.Path()).Err(err).Msg("failed to list pool volumes")
+			continue
+		}
+		for _, volume := range volumes {
+			if volume.Name() != vdiskVolumeName {
+				continue
+			}
+
+			return volume.Path(), nil
+		}
+	}
+	// none has a vdiks subvolume, we need to
+	// create one.
+	candidate := candidates[0]
+	volume, err := candidate.Pool.AddVolume(vdiskVolumeName)
+	if err != nil {
+		return path, errors.Wrap(err, "failed to create vdisk pool")
+	}
+
+	return volume.Path(), nil
 }
 
-// GetVdiskFS return the filesystem used to store the vdisk file for the VM module
-func (s *storageModule) GetVdiskFS() (pkg.Filesystem, error) {
-	return s.Path(vdiskVolumeName)
+// VDiskPools return a list of all vdisk pools
+func (s *Module) VDiskPools() ([]string, error) {
+	var paths []string
+	for _, pool := range s.pools {
+		if pool.Type() != pkg.SSDDevice {
+			continue
+		}
+
+		if _, mounted := pool.Mounted(); !mounted {
+			continue
+		}
+
+		volumes, err := pool.Volumes()
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to list pool '%s' volumes", pool.Path())
+		}
+
+		for _, volume := range volumes {
+			if volume.Name() == vdiskVolumeName {
+				paths = append(paths, volume.Path())
+			}
+		}
+	}
+
+	return paths, nil
+}
+
+// GetCacheFS return the special filesystem used by 0-OS to store internal state and flist cache
+func (s *Module) GetCacheFS() (pkg.Filesystem, error) {
+	return s.Path(cacheLabel)
 }
 
 // ensureCache creates a "cache" subvolume and mounts it in /var
-func (s *storageModule) ensureCache() error {
+func (s *Module) ensureCache() error {
 	log.Info().Msgf("Setting up cache")
 
 	log.Debug().Msgf("Checking pools for existing cache")
@@ -590,7 +653,7 @@ func (s *storageModule) ensureCache() error {
 // createSubvolWithQuota creates a subvolume with the given name and limits it to the given size
 // if the requested disk type does not have a storage pool with enough free size available, an error is returned
 // this methods does set a quota limit equal to size on the created volume
-func (s *storageModule) createSubvolWithQuota(size uint64, name string, poolType pkg.DeviceType) (filesystem.Volume, error) {
+func (s *Module) createSubvolWithQuota(size uint64, name string, poolType pkg.DeviceType) (filesystem.Volume, error) {
 	volume, err := s.createSubvol(size, name, poolType)
 	if err != nil {
 		return nil, err
@@ -607,7 +670,7 @@ func (s *storageModule) createSubvolWithQuota(size uint64, name string, poolType
 // createSubvol creates a subvolume with the given name
 // if the requested disk type does not have a storage pool with enough free size available, an error is returned
 // this method does not set any quota on the subvolume, for this uses createSubvolWithQuota
-func (s *storageModule) createSubvol(size uint64, name string, poolType pkg.DeviceType) (filesystem.Volume, error) {
+func (s *Module) createSubvol(size uint64, name string, poolType pkg.DeviceType) (filesystem.Volume, error) {
 	var err error
 
 	if poolType != pkg.HDDDevice && poolType != pkg.SSDDevice {
@@ -615,34 +678,12 @@ func (s *storageModule) createSubvol(size uint64, name string, poolType pkg.Devi
 	}
 
 	// Look for candidates in mounted pools first
-	candidates, err := s.checkForCandidates(size, poolType, true)
+	candidates, err := s.findCandidates(size, poolType)
 	if err != nil {
 		log.Error().Err(err).Msgf("failed to search candidates on mounted pools")
 		return nil, err
 	}
 
-	log.Debug().Msgf("Found %d candidates in mounted pools", len(candidates))
-
-	// If no candidates or found in mounted pools, we check the unmounted pools and get the first one that fits
-	if len(candidates) == 0 {
-		log.Debug().Msg("Checking unmounted pools")
-		candidates, err = s.checkForCandidates(size, poolType, false)
-		if err != nil {
-			log.Error().Err(err).Msgf("failed to search candidates on mounted pools")
-			return nil, err
-		}
-		log.Debug().Msgf("Found %d candidates in unmounted pools", len(candidates))
-	}
-
-	if len(candidates) == 0 {
-		return nil, pkg.ErrNotEnoughSpace{DeviceType: poolType}
-	}
-
-	sort.Slice(candidates, func(i, j int) bool {
-		// reverse sorting so most available is at beginning
-		return candidates[i].Available > candidates[j].Available
-	})
-
 	var volume filesystem.Volume
 	for _, candidate := range candidates {
 		volume, err = candidate.Pool.AddVolume(name)
@@ -662,7 +703,35 @@ type candidate struct {
 	Available uint64
 }
 
-func (s *storageModule) checkForCandidates(size uint64, poolType pkg.DeviceType, mounted bool) ([]candidate, error) {
+func (s *Module) findCandidates(size uint64, poolType pkg.DeviceType) ([]candidate, error) {
+
+	// Look for candidates in mounted pools first
+	candidates, err := s.checkForCandidates(size, poolType, true)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to search candidate on mounted pools")
+	}
+
+	log.Debug().Msgf("found %d candidates in mounted pools", len(candidates))
+
+	// If no candidates or found in mounted pools, we check the unmounted pools and get the first one that fits
+	if len(candidates) == 0 {
+		log.Debug().Msg("Checking unmounted pools")
+		candidates, err = s.checkForCandidates(size, poolType, false)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to search candidates on unmounted pools")
+		}
+
+		log.Debug().Msgf("found %d candidates in unmounted pools", len(candidates))
+	}
+
+	if len(candidates) == 0 {
+		return nil, pkg.ErrNotEnoughSpace{DeviceType: poolType}
+	}
+
+	return candidates, nil
+}
+
+func (s *Module) checkForCandidates(size uint64, poolType pkg.DeviceType, mounted bool) ([]candidate, error) {
 	var candidates []candidate
 	for _, pool := range s.pools {
 		_, poolIsMounted := pool.Mounted()
@@ -733,10 +802,17 @@ func (s *storageModule) checkForCandidates(size uint64, poolType pkg.DeviceType,
 			return candidates, nil
 		}
 	}
+
+	sort.Slice(candidates, func(i, j int) bool {
+		// reverse sorting so most available is at beginning
+		return candidates[i].Available > candidates[j].Available
+	})
+
 	return candidates, nil
 }
 
-func (s *storageModule) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
+// Monitor implements monitor method
+func (s *Module) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
 	ch := make(chan pkg.PoolsStats)
 	values := make(pkg.PoolsStats)
 	go func() {
@@ -795,7 +871,7 @@ func (s *storageModule) Monitor(ctx context.Context) <-chan pkg.PoolsStats {
 	return ch
 }
 
-func (s *storageModule) periodicallyCheckDiskShutdown() {
+func (s *Module) periodicallyCheckDiskShutdown() {
 	ticker := time.NewTicker(5 * time.Minute)
 
 	go func() {
@@ -809,7 +885,7 @@ func (s *storageModule) periodicallyCheckDiskShutdown() {
 
 // shutdownDisks will check the disks power status.
 // If a disk is on and it is not mounted then it is not supposed to be on, turn it off
-func (s *storageModule) shutdownDisks() {
+func (s *Module) shutdownDisks() {
 	for _, pool := range s.pools {
 		for _, device := range pool.Devices() {
 			log.Debug().Msgf("checking device: %s", device.Path)
diff --git a/pkg/storage/zdb.go b/pkg/storage/zdb.go
index d1e85418..67198b06 100644
--- a/pkg/storage/zdb.go
+++ b/pkg/storage/zdb.go
@@ -15,7 +15,8 @@ import (
 	"github.com/threefoldtech/zos/pkg/storage/zdbpool"
 )
 
-func (s *storageModule) Find(nsID string) (allocation pkg.Allocation, err error) {
+// Find finds a zdb namespace allocation
+func (s *Module) Find(nsID string) (allocation pkg.Allocation, err error) {
 	for _, pool := range s.pools {
 		if _, mounted := pool.Mounted(); !mounted {
 			continue
@@ -54,7 +55,7 @@ func (s *storageModule) Find(nsID string) (allocation pkg.Allocation, err error)
 // Allocate is responsible to make sure the subvolume used by a 0-db as enough storage capacity
 // of specified size, type and mode
 // it returns the volume ID and its path or an error if it couldn't allocate enough storage
-func (s *storageModule) Allocate(nsID string, diskType pkg.DeviceType, size uint64, mode pkg.ZDBMode) (allocation pkg.Allocation, err error) {
+func (s *Module) Allocate(nsID string, diskType pkg.DeviceType, size uint64, mode pkg.ZDBMode) (allocation pkg.Allocation, err error) {
 	log := log.With().
 		Str("type", string(diskType)).
 		Uint64("size", size).
@@ -163,7 +164,7 @@ type zdbcandidate struct {
 	Free uint64
 }
 
-func (s *storageModule) checkForZDBCandidateVolumes(size uint64, poolType pkg.DeviceType, targetMode zdbpool.IndexMode) ([]zdbcandidate, error) {
+func (s *Module) checkForZDBCandidateVolumes(size uint64, poolType pkg.DeviceType, targetMode zdbpool.IndexMode) ([]zdbcandidate, error) {
 	var candidates []zdbcandidate
 	for _, pool := range s.pools {
 		// ignore pools that are not mounted for now
diff --git a/pkg/stubs/storage_stub.go b/pkg/stubs/storage_stub.go
index 4b52f7e2..f2d47a91 100644
--- a/pkg/stubs/storage_stub.go
+++ b/pkg/stubs/storage_stub.go
@@ -2,6 +2,7 @@ package stubs
 
 import (
 	"context"
+
 	zbus "github.com/threefoldtech/zbus"
 	pkg "github.com/threefoldtech/zos/pkg"
 )
@@ -111,22 +112,6 @@ func (s *StorageModuleStub) GetCacheFS() (ret0 pkg.Filesystem, ret1 error) {
 	return
 }
 
-func (s *StorageModuleStub) GetVdiskFS() (ret0 pkg.Filesystem, ret1 error) {
-	args := []interface{}{}
-	result, err := s.client.Request(s.module, s.object, "GetVdiskFS", args...)
-	if err != nil {
-		panic(err)
-	}
-	if err := result.Unmarshal(0, &ret0); err != nil {
-		panic(err)
-	}
-	ret1 = new(zbus.RemoteError)
-	if err := result.Unmarshal(1, &ret1); err != nil {
-		panic(err)
-	}
-	return
-}
-
 func (s *StorageModuleStub) ListFilesystems() (ret0 []pkg.Filesystem, ret1 error) {
 	args := []interface{}{}
 	result, err := s.client.Request(s.module, s.object, "ListFilesystems", args...)
diff --git a/pkg/upgrade/boot.go b/pkg/upgrade/boot.go
index 8e4bbcc9..771a6599 100644
--- a/pkg/upgrade/boot.go
+++ b/pkg/upgrade/boot.go
@@ -65,20 +65,20 @@ func (b *Boot) Name() string {
 }
 
 //CurrentBins returns a list of current binaries installed
-func (b *Boot) CurrentBins() (map[string]RepoFList, error) {
+func (b *Boot) CurrentBins() (map[string]FListInfo, error) {
 	f, err := os.Open(BinariesFile)
 	if err != nil {
 		return nil, err
 	}
 	dec := json.NewDecoder(f)
 
-	var result map[string]RepoFList
+	var result map[string]FListInfo
 	err = dec.Decode(&result)
 	return result, err
 }
 
 //SetBins sets the current list of binaries in boot files
-func (b *Boot) SetBins(current map[string]RepoFList) error {
+func (b *Boot) SetBins(current map[string]FListInfo) error {
 	f, err := os.Create(BinariesFile)
 	if err != nil {
 		return err
@@ -128,7 +128,7 @@ func (b *Boot) MustVersion() semver.Version {
 }
 
 // loadInfo get boot info set by bootstrap process
-func loadInfo(fqn string, path string) (info flistInfo, err error) {
+func loadInfo(fqn string, path string) (info FullFListInfo, err error) {
 	info.Repository = filepath.Dir(fqn)
 	f, err := os.Open(path)
 	if err != nil {
diff --git a/pkg/upgrade/file.go b/pkg/upgrade/file.go
deleted file mode 100644
index 069b6f04..00000000
--- a/pkg/upgrade/file.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package upgrade
-
-import (
-	"os"
-)
-
-func exists(path string) bool {
-	_, err := os.Stat(path)
-	return err == nil
-}
diff --git a/pkg/upgrade/hub.go b/pkg/upgrade/hub.go
index 1d127a63..ba062b2c 100644
--- a/pkg/upgrade/hub.go
+++ b/pkg/upgrade/hub.go
@@ -12,6 +12,9 @@ import (
 	"strings"
 
 	"github.com/blang/semver"
+	"github.com/pkg/errors"
+	"github.com/rs/zerolog/log"
+	"github.com/threefoldtech/0-fs/meta"
 )
 
 const (
@@ -22,11 +25,11 @@ const (
 	hubStorage = "zdb://hub.grid.tf:9900"
 )
 
-// hubClient API for f-list
-type hubClient struct{}
+// HubClient API for f-list
+type HubClient struct{}
 
 // MountURL returns the full url of given flist.
-func (h *hubClient) MountURL(flist string) string {
+func (h *HubClient) MountURL(flist string) string {
 	url, err := url.Parse(hubBaseURL)
 	if err != nil {
 		panic("invalid base url")
@@ -36,12 +39,12 @@ func (h *hubClient) MountURL(flist string) string {
 }
 
 // StorageURL return hub storage url
-func (h *hubClient) StorageURL() string {
+func (h *HubClient) StorageURL() string {
 	return hubStorage
 }
 
 // Info gets flist info from hub
-func (h *hubClient) Info(flist string) (info flistInfo, err error) {
+func (h *HubClient) Info(flist string) (info FullFListInfo, err error) {
 	u, err := url.Parse(hubBaseURL)
 	if err != nil {
 		panic("invalid base url")
@@ -59,7 +62,7 @@ func (h *hubClient) Info(flist string) (info flistInfo, err error) {
 	defer ioutil.ReadAll(response.Body)
 
 	if response.StatusCode != http.StatusOK {
-		return info, fmt.Errorf("failed to get flist info: %s", response.Status)
+		return info, fmt.Errorf("failed to get flist (%s) info: %s", flist, response.Status)
 	}
 	defer response.Body.Close()
 
@@ -69,7 +72,8 @@ func (h *hubClient) Info(flist string) (info flistInfo, err error) {
 	return info, err
 }
 
-func (h *hubClient) List(repo string) ([]listFListInfo, error) {
+// List list repo flists
+func (h *HubClient) List(repo string) ([]FListInfo, error) {
 	u, err := url.Parse(hubBaseURL)
 	if err != nil {
 		panic("invalid base url")
@@ -90,7 +94,7 @@ func (h *hubClient) List(repo string) ([]listFListInfo, error) {
 
 	dec := json.NewDecoder(response.Body)
 
-	var result []listFListInfo
+	var result []FListInfo
 	err = dec.Decode(&result)
 
 	for i := range result {
@@ -100,7 +104,63 @@ func (h *hubClient) List(repo string) ([]listFListInfo, error) {
 	return result, err
 }
 
-type listFListInfo struct {
+// Download downloads an flist (fqn: repo/name) to cache and return the full
+// path to the extraced meta data directory. the returned path is in format
+// {cache}/{hash}/
+func (h *HubClient) Download(cache, flist string) (string, error) {
+	var info FullFListInfo
+	for {
+		var err error
+		info, err = h.Info(flist)
+		if err != nil {
+			return "", err
+		}
+		if info.Type == "symlink" {
+			flist = filepath.Join(filepath.Dir(flist), info.Target)
+		} else if info.Type == "regular" {
+			break
+		} else {
+			return "", fmt.Errorf("unknown flist type: %s", info.Type)
+		}
+	}
+
+	if info.Hash == "" {
+		return "", fmt.Errorf("invalid flist info returned")
+	}
+
+	const (
+		dbFileName = "flistdb.sqlite3"
+	)
+
+	// check if already downloaded
+	extracted := filepath.Join(cache, info.Hash)
+	if _, err := os.Stat(filepath.Join(extracted, dbFileName)); err == nil {
+		// already exists.
+		return extracted, nil
+	}
+
+	u, err := url.Parse(hubBaseURL)
+	if err != nil {
+		panic("invalid base url")
+	}
+
+	u.Path = flist
+	log.Debug().Str("url", u.String()).Msg("downloading flist")
+	response, err := http.Get(u.String())
+	if err != nil {
+		return extracted, errors.Wrap(err, "failed to download flist")
+	}
+
+	defer response.Body.Close()
+	if response.StatusCode != http.StatusOK {
+		return extracted, fmt.Errorf("failed to download flist: %s", response.Status)
+	}
+
+	return extracted, meta.Unpack(response.Body, extracted)
+}
+
+// FListInfo is information of flist as returned by repo list operation
+type FListInfo struct {
 	Name       string `json:"name"`
 	Target     string `json:"target"`
 	Type       string `json:"type"`
@@ -108,21 +168,21 @@ type listFListInfo struct {
 	Repository string `json:"-"`
 }
 
-// flistInfo reflects node boot information (flist + version)
-type flistInfo struct {
-	listFListInfo
-	Hash string `json:"hash"`
+// FullFListInfo reflects node boot information (flist + version)
+type FullFListInfo struct {
+	FListInfo
+	Hash string `json:"md5"`
 	Size uint64 `json:"size"`
 }
 
-// fileInfo is the file of an flist
-type fileInfo struct {
+// FileInfo is the file of an flist
+type FileInfo struct {
 	Path string `json:"path"`
 	Size uint64 `json:"size"`
 }
 
 // Commit write version to version file
-func (b *flistInfo) Commit(path string) error {
+func (b *FullFListInfo) Commit(path string) error {
 	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0400)
 	if err != nil {
 		return err
@@ -133,11 +193,12 @@ func (b *flistInfo) Commit(path string) error {
 	return enc.Encode(b)
 }
 
-func (b *listFListInfo) Fqdn() string {
+// Fqdn return the full flist name
+func (b *FListInfo) Fqdn() string {
 	return path.Join(b.Repository, b.Name)
 }
 
-func (b *listFListInfo) extractVersion(name string) (ver semver.Version, err error) {
+func (b *FListInfo) extractVersion(name string) (ver semver.Version, err error) {
 	// name is suppose to be as follows
 	// <name>:<version>.flist
 	parts := strings.Split(name, ":")
@@ -148,7 +209,7 @@ func (b *listFListInfo) extractVersion(name string) (ver semver.Version, err err
 }
 
 // Version returns the version of the flist
-func (b *listFListInfo) Version() (semver.Version, error) {
+func (b *FListInfo) Version() (semver.Version, error) {
 	// computing the version is tricky because it's part of the name
 	// of the flist (or the target) depends on the type of the flist
 
@@ -160,7 +221,7 @@ func (b *listFListInfo) Version() (semver.Version, error) {
 }
 
 // Absolute returns the actual flist name
-func (b *listFListInfo) Absolute() string {
+func (b *FListInfo) Absolute() string {
 	name := b.Name
 	if b.Type == "symlink" {
 		name = b.Target
@@ -170,14 +231,14 @@ func (b *listFListInfo) Absolute() string {
 }
 
 // Files gets the list of the files of an flist
-func (b *listFListInfo) Files() ([]fileInfo, error) {
+func (b *FListInfo) Files() ([]FileInfo, error) {
 	flist := b.Absolute()
 	if len(flist) == 0 {
 		return nil, fmt.Errorf("invalid flist info")
 	}
 
 	var content struct {
-		Content []fileInfo `json:"content"`
+		Content []FileInfo `json:"content"`
 	}
 
 	u, err := url.Parse(hubBaseURL)
diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go
index 6c400d0a..31de54b0 100644
--- a/pkg/upgrade/upgrade.go
+++ b/pkg/upgrade/upgrade.go
@@ -2,8 +2,6 @@ package upgrade
 
 import (
 	"fmt"
-	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -11,11 +9,12 @@ import (
 
 	"github.com/pkg/errors"
 
+	"github.com/threefoldtech/0-fs/meta"
+	"github.com/threefoldtech/0-fs/rofs"
+	"github.com/threefoldtech/0-fs/storage"
 	"github.com/threefoldtech/zos/pkg/zinit"
 
 	"github.com/rs/zerolog/log"
-
-	"github.com/threefoldtech/zos/pkg"
 )
 
 var (
@@ -28,13 +27,87 @@ var (
 	flistIdentityPath = "/bin/identityd"
 )
 
+const (
+	defaultHubStorage  = "zdb://hub.grid.tf:9900"
+	defaultZinitSocket = "/var/run/zinit.sock"
+)
+
 // Upgrader is the component that is responsible
 // to keep 0-OS up to date
 type Upgrader struct {
-	FLister      pkg.Flister
-	Zinit        *zinit.Client
-	NoSelfUpdate bool
-	hub          hubClient
+	zinit        *zinit.Client
+	cache        string
+	noSelfUpdate bool
+	hub          HubClient
+	storage      storage.Storage
+}
+
+// UpgraderOption interface
+type UpgraderOption func(u *Upgrader) error
+
+// NoSelfUpgrade option
+func NoSelfUpgrade(o bool) UpgraderOption {
+	return func(u *Upgrader) error {
+		u.noSelfUpdate = o
+
+		return nil
+	}
+}
+
+// Storage option overrides the default hub storage url
+func Storage(url string) func(u *Upgrader) error {
+	return func(u *Upgrader) error {
+		storage, err := storage.NewSimpleStorage(url)
+		if err != nil {
+			return errors.Wrap(err, "failed to initialize hub storage")
+		}
+		u.storage = storage
+		return nil
+	}
+}
+
+// Zinit option overrides the default zinit socket
+func Zinit(socket string) func(u *Upgrader) error {
+	return func(u *Upgrader) error {
+		zinit, err := zinit.New(defaultZinitSocket)
+		if err != nil {
+			return errors.Wrap(err, "failed to initialize connection to zinit")
+		}
+		u.zinit = zinit
+		return nil
+	}
+}
+
+// NewUpgrader creates a new upgrader instance
+func NewUpgrader(cache string, opts ...UpgraderOption) (*Upgrader, error) {
+	u := &Upgrader{
+		cache: cache,
+	}
+
+	for _, opt := range opts {
+		if err := opt(u); err != nil {
+			return nil, err
+		}
+	}
+
+	if u.storage == nil {
+		// no storage option was set. use default
+		if err := Storage(defaultHubStorage)(u); err != nil {
+			return nil, err
+		}
+	}
+
+	if u.zinit == nil {
+		if err := Zinit(defaultZinitSocket)(u); err != nil {
+			return nil, err
+		}
+	}
+
+	return u, nil
+}
+
+func (u *Upgrader) flistCache() string {
+	return filepath.Join(u.cache, "flist")
 }
 
 // Upgrade is the method that does a full upgrade flow
@@ -46,46 +119,76 @@ func (u *Upgrader) Upgrade(from, to FListEvent) error {
 	return u.applyUpgrade(from, to)
 }
 
+// getFlist accepts fqdn of flist as `<repo>/<name>.flist`
+func (u *Upgrader) getFlist(flist string) (meta.Walker, error) {
+	db, err := u.hub.Download(u.flistCache(), flist)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to download flist")
+	}
+
+	store, err := meta.NewStore(db)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to load flist db")
+	}
+
+	walker, ok := store.(meta.Walker)
+	if !ok {
+		store.Close()
+		return nil, errors.Wrap(err, "flist database of unsupported type")
+	}
+
+	return walker, nil
+}
+
 // InstallBinary from a single flist.
-func (u *Upgrader) InstallBinary(flist RepoFList) error {
+func (u *Upgrader) InstallBinary(flist FListInfo) error {
 	log.Info().Str("flist", flist.Fqdn()).Msg("start applying upgrade")
 
-	flistRoot, err := u.FLister.Mount(u.hub.MountURL(flist.Fqdn()), u.hub.StorageURL(), pkg.ReadOnlyMountOptions)
+	store, err := u.getFlist(flist.Fqdn())
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "failed to process flist: %s", flist.Fqdn())
 	}
+	defer store.Close()
 
-	defer func() {
-		if err := u.FLister.Umount(flistRoot); err != nil {
-			log.Error().Err(err).Msgf("fail to umount flist at %s: %v", flistRoot, err)
-		}
-	}()
-
-	if err := copyRecursive(flistRoot, "/"); err != nil {
+	if err := u.copyRecursive(store, "/"); err != nil {
 		return errors.Wrapf(err, "failed to install flist: %s", flist.Fqdn())
 	}
 
-	p := filepath.Join(flistRoot, "etc", "zinit")
-	log.Debug().Str("path", p).Msg("checking for zinit unit files")
-	files, err := ioutil.ReadDir(p)
-	if os.IsNotExist(err) {
-		log.Debug().Err(err).Msg("/etc/zinit not found on flist")
-		return nil
-	} else if err != nil {
-		return errors.Wrap(err, "failed to list package services")
+	services, err := u.servicesFromStore(store)
+	if err != nil {
+		return errors.Wrap(err, "failed to list services from flist")
 	}
 
+	return u.ensureRestarted(services...)
+}
+
+func (u *Upgrader) servicesFromStore(store meta.Walker) ([]string, error) {
+	const zinitPath = "/etc/zinit"
+
 	var services []string
-	for _, file := range files {
-		if !strings.HasSuffix(file.Name(), ".yaml") {
-			continue
+	err := store.Walk(zinitPath, func(path string, info meta.Meta) error {
+		if info.IsDir() {
+			return nil
+		}
+		dir := filepath.Dir(path)
+		if dir != zinitPath {
+			return nil
+		}
+
+		if !strings.HasSuffix(info.Name(), ".yaml") {
+			return nil
 		}
 
-		service := strings.TrimSuffix(file.Name(), ".yaml")
-		services = append(services, service)
+		services = append(services,
+			strings.TrimSuffix(info.Name(), ".yaml"))
+		return nil
+	})
+
+	if err == meta.ErrNotFound {
+		return nil, nil
 	}
 
-	return u.ensureRestarted(services...)
+	return services, err
 }
 
 func (u *Upgrader) ensureRestarted(service ...string) error {
@@ -100,11 +203,11 @@ func (u *Upgrader) ensureRestarted(service ...string) error {
 	}
 
 	for _, name := range service {
-		if err := u.Zinit.Forget(name); err != nil {
+		if err := u.zinit.Forget(name); err != nil {
 			log.Warn().Err(err).Str("service", name).Msg("could not forget service")
 		}
 
-		if err := u.Zinit.Monitor(name); err != nil {
+		if err := u.zinit.Monitor(name); err != nil {
 			log.Error().Err(err).Str("service", name).Msg("could not monitor service")
 		}
 	}
@@ -113,15 +216,15 @@ func (u *Upgrader) ensureRestarted(service ...string) error {
 }
 
 // UninstallBinary  from a single flist.
-func (u *Upgrader) UninstallBinary(flist RepoFList) error {
-	return u.uninstall(flist.listFListInfo)
+func (u *Upgrader) UninstallBinary(flist FListInfo) error {
+	return u.uninstall(flist)
 }
 
 func (u Upgrader) stopMultiple(timeout time.Duration, service ...string) error {
 	services := make(map[string]struct{})
 	for _, name := range service {
 		log.Info().Str("service", name).Msg("stopping service")
-		if err := u.Zinit.Stop(name); err != nil {
+		if err := u.zinit.Stop(name); err != nil {
 			log.Debug().Str("service", name).Msg("service undefined")
 			continue
 		}
@@ -135,7 +238,7 @@ func (u Upgrader) stopMultiple(timeout time.Duration, service ...string) error {
 		var stopped []string
 		for service := range services {
 			log.Info().Str("service", service).Msg("check if service is stopped")
-			status, err := u.Zinit.Status(service)
+			status, err := u.zinit.Status(service)
 			if err != nil {
 				return err
 			}
@@ -167,7 +270,7 @@ func (u Upgrader) stopMultiple(timeout time.Duration, service ...string) error {
 		case <-deadline:
 			for service := range services {
 				log.Warn().Str("service", service).Msg("service didn't stop in time. use SIGKILL")
-				if err := u.Zinit.Kill(service, zinit.SIGKILL); err != nil {
+				if err := u.zinit.Kill(service, zinit.SIGKILL); err != nil {
 					log.Error().Err(err).Msgf("failed to send SIGKILL to service %s", service)
 				}
 			}
@@ -183,8 +286,8 @@ func (u Upgrader) stopMultiple(timeout time.Duration, service ...string) error {
 // it will copy the new binary and ask for a restart.
 // next time this method is called, it will match the flist
 // revision, and hence will continue updating all the other daemons
-func (u *Upgrader) upgradeSelf(root string) error {
-	if u.NoSelfUpdate {
+func (u *Upgrader) upgradeSelf(store meta.Walker) error {
+	if u.noSelfUpdate {
 		log.Debug().Msg("skipping self upgrade")
 		return nil
 	}
@@ -192,9 +295,10 @@ func (u *Upgrader) upgradeSelf(root string) error {
 	current := currentRevision()
 	log.Debug().Str("revision", current).Msg("current revision")
 
-	bin := filepath.Join(root, currentBinPath())
+	bin := currentBinPath()
+	info, exists := store.Get(bin)
 
-	if !exists(bin) {
+	if !exists {
 		// no bin for update daemon in the flist.
 		log.Debug().Str("bin", bin).Msg("binary file does not exist")
 		return nil
@@ -217,7 +321,7 @@ func (u *Upgrader) upgradeSelf(root string) error {
 		return nil
 	}
 
-	if err := copyFile(currentBinPath(), bin); err != nil {
+	if err := u.copyFile(bin, info); err != nil {
 		return err
 	}
 
@@ -225,7 +329,7 @@ func (u *Upgrader) upgradeSelf(root string) error {
 	return ErrRestartNeeded
 }
 
-func (u *Upgrader) uninstall(flist listFListInfo) error {
+func (u *Upgrader) uninstall(flist FListInfo) error {
 	files, err := flist.Files()
 	if err != nil {
 		return errors.Wrapf(err, "failed to get list of current installed files for '%s'", flist.Absolute())
@@ -262,7 +366,7 @@ func (u *Upgrader) uninstall(flist listFListInfo) error {
 	// we do a forget so any changes of the zinit config
 	// themselves get reflected once monitored again
 	for _, name := range names {
-		if err := u.Zinit.Forget(name); err != nil {
+		if err := u.zinit.Forget(name); err != nil {
 			log.Error().Err(err).Str("service", name).Msg("error on zinit forget")
 		}
 	}
@@ -296,98 +400,77 @@ func (u *Upgrader) uninstall(flist listFListInfo) error {
 func (u *Upgrader) applyUpgrade(from, to FListEvent) error {
 	log.Info().Str("flist", to.Fqdn()).Str("version", to.TryVersion().String()).Msg("start applying upgrade")
 
-	flistRoot, err := u.FLister.Mount(u.hub.MountURL(to.Fqdn()), u.hub.StorageURL(), pkg.ReadOnlyMountOptions)
+	store, err := u.getFlist(to.Fqdn())
 	if err != nil {
-		return err
+		return errors.Wrap(err, "failed to get flist store")
 	}
 
-	defer func() {
-		if err := u.FLister.Umount(flistRoot); err != nil {
-			log.Error().Err(err).Msgf("fail to umount flist at %s: %v", flistRoot, err)
-		}
-	}()
+	defer store.Close()
 
-	if err := u.upgradeSelf(flistRoot); err != nil {
+	if err := u.upgradeSelf(store); err != nil {
 		return err
 	}
 
-	if err := u.uninstall(from.listFListInfo); err != nil {
+	if err := u.uninstall(from.FListInfo); err != nil {
 		log.Error().Err(err).Msg("failed to unistall current flist. Upgraded anyway")
 	}
 
 	log.Info().Msg("clean up complete, copying new files")
-	// once the flist is mounted we can inspect
-	// it for all zinit config files.
-	files, err := ioutil.ReadDir(filepath.Join(flistRoot, "etc", "zinit"))
+	services, err := u.servicesFromStore(store)
 	if err != nil {
-		return errors.Wrap(err, "invalid flist. no zinit services")
-	}
-
-	var names []string
-	for _, service := range files {
-		name := service.Name()
-		if service.IsDir() || !strings.HasSuffix(name, ".yaml") {
-			continue
-		}
-
-		names = append(names, strings.TrimSuffix(name, ".yaml"))
+		return err
 	}
-
-	log.Debug().Strs("services", names).Msg("new services")
-
-	if err := copyRecursive(flistRoot, "/", flistIdentityPath); err != nil {
+	if err := u.copyRecursive(store, "/", flistIdentityPath); err != nil {
 		return err
 	}
 
 	log.Debug().Msg("copying files complete")
 	// start all services in the flist
-	for _, name := range names {
-		if err := u.Zinit.Monitor(name); err != nil {
-			log.Error().Err(err).Str("service", name).Msg("error on zinit monitor")
+	for _, service := range services {
+		if err := u.zinit.Monitor(service); err != nil {
+			log.Error().Err(err).Str("service", service).Msg("error on zinit monitor")
 		}
 
 		// while we totally do not need to call start after monitor but
 		// monitor won't take an action on a monitored service if it's
 		// stopped (but not forgoten). So we call start just to be sure
-		if err := u.Zinit.Start(name); err != nil {
-			log.Error().Err(err).Str("service", name).Msg("error on zinit start")
+		if err := u.zinit.Start(service); err != nil {
+			log.Error().Err(err).Str("service", service).Msg("error on zinit start")
 		}
 	}
 
 	return nil
 }
 
-func copyRecursive(source string, destination string, skip ...string) error {
-	return filepath.Walk(source, func(path string, info os.FileInfo, _ error) error {
-		rel, err := filepath.Rel(source, path)
-		if err != nil {
-			return err
-		}
+func (u *Upgrader) copyRecursive(store meta.Walker, destination string, skip ...string) error {
+	return store.Walk("", func(path string, info meta.Meta) error {
 
-		dest := filepath.Join(destination, rel)
+		dest := filepath.Join(destination, path)
 		if isIn(dest, skip) {
 			if info.IsDir() {
-				return filepath.SkipDir
+				return meta.ErrSkipDir
 			}
 			log.Debug().Str("file", dest).Msg("skipping file")
 			return nil
 		}
 
 		if info.IsDir() {
-			if err := os.MkdirAll(dest, info.Mode()); err != nil {
+
+			if err := os.MkdirAll(dest, os.FileMode(info.Info().Access.Mode)); err != nil {
 				return err
 			}
-		} else if info.Mode().IsRegular() {
+		} else if info.Info().Type == meta.RegularType {
 			// regular file (or other types that we don't handle)
-			if err := copyFile(dest, path); err != nil {
+			if err := u.copyFile(dest, info); err != nil {
 				return err
 			}
 		} else {
-			log.Debug().Str("type", info.Mode().String()).Msg("ignoring not suppored file type")
+			log.Debug().Str("type", info.Info().Type.String()).Msg("ignoring not suppored file type")
 		}
 
 		return nil
 	})
+
 }
 
 func isIn(target string, list []string) bool {
@@ -399,8 +482,8 @@ func isIn(target string, list []string) bool {
 	return false
 }
 
-func copyFile(dst, src string) error {
-	log.Info().Str("source", src).Str("destination", dst).Msg("copy file")
+func (u *Upgrader) copyFile(dst string, src meta.Meta) error {
+	log.Info().Str("source", src.Name()).Str("destination", dst).Msg("copy file")
 
 	var (
 		isNew  = false
@@ -413,36 +496,37 @@ func copyFile(dst, src string) error {
 		isNew = true
 	}
 
+	var err error
 	if !isNew {
 		dstOld = dst + ".old"
 		if err := os.Rename(dst, dstOld); err != nil {
 			return err
 		}
-	}
 
-	fSrc, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-	defer fSrc.Close()
+		defer func() {
+			if err == nil {
+				if err := os.Remove(dstOld); err != nil {
+					log.Error().Err(err).Str("file", dstOld).Msg("failed to clean up backup file")
+				}
+				return
+			}
 
-	stat, err := fSrc.Stat()
-	if err != nil {
-		return err
+			if err := os.Rename(dstOld, dst); err != nil {
+				log.Error().Err(err).Str("file", dst).Msg("failed to restore file after a failed download")
+			}
+		}()
 	}
 
-	fDst, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, stat.Mode().Perm())
+	downloader := rofs.NewDownloader(u.storage, src)
+	fDst, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, os.FileMode(src.Info().Access.Mode))
 	if err != nil {
 		return err
 	}
 	defer fDst.Close()
 
-	if _, err = io.Copy(fDst, fSrc); err != nil {
+	if err = downloader.Download(fDst); err != nil {
 		return err
 	}
 
-	if !isNew {
-		return os.Remove(dstOld)
-	}
 	return nil
 }
diff --git a/pkg/upgrade/watcher.go b/pkg/upgrade/watcher.go
index 49536aa7..0d6fb8b2 100644
--- a/pkg/upgrade/watcher.go
+++ b/pkg/upgrade/watcher.go
@@ -39,7 +39,7 @@ type Watcher interface {
 
 //FListEvent struct
 type FListEvent struct {
-	flistInfo
+	FullFListInfo
 }
 
 //EventType of the event
@@ -62,7 +62,7 @@ type FListSemverWatcher struct {
 	Duration time.Duration
 	Current  semver.Version
 
-	client hubClient
+	client HubClient
 }
 
 var _ Watcher = &FListSemverWatcher{}
@@ -89,7 +89,7 @@ func (w *FListSemverWatcher) Watch(ctx context.Context) (<-chan Event, error) {
 
 	if version.GT(w.Current) {
 		ch <- &FListEvent{
-			flistInfo: info,
+			FullFListInfo: info,
 		}
 		w.Current = version
 	}
@@ -124,7 +124,7 @@ func (w *FListSemverWatcher) Watch(ctx context.Context) (<-chan Event, error) {
 			if version.GT(w.Current) {
 				select {
 				case ch <- &FListEvent{
-					flistInfo: info,
+					FullFListInfo: info,
 				}:
 				case <-ctx.Done():
 					return
@@ -138,16 +138,11 @@ func (w *FListSemverWatcher) Watch(ctx context.Context) (<-chan Event, error) {
 	return ch, nil
 }
 
-//RepoFList holds information of flist from a repo list operation
-type RepoFList struct {
-	listFListInfo
-}
-
 //RepoEvent is returned by the repo watcher
 type RepoEvent struct {
 	Repo  string
-	ToAdd []RepoFList
-	ToDel []RepoFList
+	ToAdd []FListInfo
+	ToDel []FListInfo
 }
 
 //EventType returns event type
@@ -158,28 +153,28 @@ func (e *RepoEvent) EventType() EventType {
 //FListRepoWatcher type
 type FListRepoWatcher struct {
 	Repo     string
-	Current  map[string]RepoFList
+	Current  map[string]FListInfo
 	Duration time.Duration
 
-	client hubClient
+	client HubClient
 }
 
-func (w *FListRepoWatcher) list() (map[string]RepoFList, error) {
+func (w *FListRepoWatcher) list() (map[string]FListInfo, error) {
 	packages, err := w.client.List(w.Repo)
 	if err != nil {
 		return nil, err
 	}
 
-	result := make(map[string]RepoFList)
+	result := make(map[string]FListInfo)
 	for _, pkg := range packages {
-		flist := RepoFList{pkg}
-		result[flist.Fqdn()] = flist
+		//flist := FListInfo{pkg}
+		result[pkg.Fqdn()] = pkg
 	}
 
 	return result, nil
 }
 
-func (w *FListRepoWatcher) diff(packages map[string]RepoFList) (toAdd, toDel []RepoFList) {
+func (w *FListRepoWatcher) diff(packages map[string]FListInfo) (toAdd, toDel []FListInfo) {
 	for name, pkg := range packages {
 		current, ok := w.Current[name]
 		if !ok || pkg.Updated != current.Updated {
@@ -198,7 +193,7 @@ func (w *FListRepoWatcher) diff(packages map[string]RepoFList) (toAdd, toDel []R
 }
 
 // Diff return the remote changes related to current list of packages
-func (w *FListRepoWatcher) Diff() (all map[string]RepoFList, toAdd, toDell []RepoFList, err error) {
+func (w *FListRepoWatcher) Diff() (all map[string]FListInfo, toAdd, toDell []FListInfo, err error) {
 	all, err = w.list()
 	if err != nil {
 		return all, nil, nil, errors.Wrap(err, "failed to get available packages")
diff --git a/tools/Makefile b/tools/Makefile
index 833af0be..d9e8fc51 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -13,7 +13,7 @@ all: _base explorer
 
 getdeps:
 	@echo "Installing golint" && go install golang.org/x/lint/golint
-	@echo "Installing gocyclo" && go install github.com/fzipp/gocyclo
+	@echo "Installing gocyclo" && go install github.com/fzipp/gocyclo/cmd/gocyclo
 	@echo "Installing misspell" && go install github.com/client9/misspell/cmd/misspell
 	@echo "Installing ineffassign" && go install github.com/gordonklaus/ineffassign
 	@echo "Installing statik" && go install github.com/rakyll/statik


Test patch:
diff --git a/pkg/flist/flist_test.go b/pkg/flist/flist_test.go
index e4a65753..22af3cc9 100644
--- a/pkg/flist/flist_test.go
+++ b/pkg/flist/flist_test.go
@@ -58,6 +58,11 @@ func (s *StorageMock) Path(name string) (pkg.Filesystem, error) {
 	}, args.Error(1)
 }
 
+func (s *StorageMock) CanAllocate(name string, size uint64) (bool, error) {
+	args := s.Called(name, size)
+	return args.Bool(0), args.Error(1)
+}
+
 // GetCacheFS return the special filesystem used by 0-OS to store internal state and flist cache
 func (s *StorageMock) GetCacheFS() (pkg.Filesystem, error) {
 	return pkg.Filesystem{}, nil
diff --git a/pkg/gedis/gedis_test.go b/pkg/gedis/gedis_test.go
index 209565cc..0762c90d 100644
--- a/pkg/gedis/gedis_test.go
+++ b/pkg/gedis/gedis_test.go
@@ -4,8 +4,6 @@ import (
 	"encoding/json"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	"github.com/stretchr/testify/require"
 
 	"github.com/gomodule/redigo/redis"
@@ -79,21 +77,6 @@ func mustMarshal(t *testing.T, v interface{}) []byte {
 	return r
 }
 
-func EqualJSON(t *testing.T, expected, actual []byte) {
-	var exp interface{}
-	var act interface{}
-
-	if err := json.Unmarshal(expected, &exp); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := json.Unmarshal(actual, &act); err != nil {
-		t.Fatal(err)
-	}
-
-	assert.Equal(t, exp, act)
-}
-
 func TestGedisSend(t *testing.T) {
 	require := require.New(t)
 
diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go
index cd002a11..a13063a5 100644
--- a/pkg/storage/storage_test.go
+++ b/pkg/storage/storage_test.go
@@ -13,7 +13,8 @@ import (
 
 type testVolume struct {
 	mock.Mock
-	name string
+	name  string
+	usage filesystem.Usage
 }
 
 func (p *testVolume) ID() int {
@@ -25,7 +26,7 @@ func (p *testVolume) Path() string {
 }
 
 func (p *testVolume) Usage() (filesystem.Usage, error) {
-	return filesystem.Usage{}, fmt.Errorf("not implemented")
+	return p.usage, nil
 }
 
 func (p *testVolume) Limit(size uint64) error {
@@ -80,23 +81,23 @@ func (p *testPool) Mounted() (string, bool) {
 }
 
 func (p *testPool) Mount() (string, error) {
-	return "", fmt.Errorf("not implemented")
+	return "", fmt.Errorf("Mount not implemented")
 }
 
 func (p *testPool) MountWithoutScan() (string, error) {
-	return "", fmt.Errorf("not implemented")
+	return "", fmt.Errorf("MountWithoutScan not implemented")
 }
 
 func (p *testPool) UnMount() error {
-	return fmt.Errorf("not implemented")
+	return fmt.Errorf("UnMount not implemented")
 }
 
 func (p *testPool) AddDevice(_ *filesystem.Device) error {
-	return fmt.Errorf("not implemented")
+	return fmt.Errorf("AddDevice not implemented")
 }
 
 func (p *testPool) RemoveDevice(_ *filesystem.Device) error {
-	return fmt.Errorf("not implemented")
+	return fmt.Errorf("RemoveDevice not implemented")
 }
 
 func (p *testPool) Type() pkg.DeviceType {
@@ -163,7 +164,7 @@ func TestCreateSubvol(t *testing.T) {
 		ptype: pkg.HDDDevice,
 	}
 
-	mod := storageModule{
+	mod := Module{
 		pools: []filesystem.Pool{
 			pool1, pool2, pool3,
 		},
@@ -218,7 +219,7 @@ func TestCreateSubvolUnlimited(t *testing.T) {
 		ptype: pkg.HDDDevice,
 	}
 
-	mod := storageModule{
+	mod := Module{
 		pools: []filesystem.Pool{
 			pool1, pool2, pool3,
 		},
@@ -273,7 +274,7 @@ func TestCreateSubvolNoSpaceLeft(t *testing.T) {
 		ptype: pkg.HDDDevice,
 	}
 
-	mod := storageModule{
+	mod := Module{
 		pools: []filesystem.Pool{
 			pool1, pool2, pool3,
 		},
@@ -286,3 +287,169 @@ func TestCreateSubvolNoSpaceLeft(t *testing.T) {
 
 	require.EqualError(err, "Not enough space left in pools of this type ssd")
 }
+
+func TestVDiskFindCandidatesHasEnoughSpace(t *testing.T) {
+	require := require.New(t)
+
+	pool1 := &testPool{
+		name:     "pool-1",
+		reserved: 2000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool2 := &testPool{
+		name:     "pool-2",
+		reserved: 1000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool3 := &testPool{
+		name:     "pool-3",
+		reserved: 0,
+		usage: filesystem.Usage{
+			Size: 100000,
+			Used: 0,
+		},
+		ptype: pkg.HDDDevice,
+	}
+
+	mod := Module{
+		pools: []filesystem.Pool{
+			pool1, pool2, pool3,
+		},
+	}
+
+	sub := &testVolume{
+		name: vdiskVolumeName,
+	}
+
+	// pool3.On("AddVolume", "sub").Return(sub, nil)
+	// sub.On("Limit", uint64(0)).Return(nil)
+
+	pool1.On("Volumes").Return([]filesystem.Volume{sub}, nil)
+	pool2.On("Volumes").Return([]filesystem.Volume{}, nil)
+	pool3.On("Volumes").Return([]filesystem.Volume{}, nil)
+
+	_, err := mod.VDiskFindCandidate(500)
+
+	require.NoError(err)
+}
+
+func TestVDiskFindCandidatesWrongType(t *testing.T) {
+	require := require.New(t)
+
+	pool1 := &testPool{
+		name:     "pool-1",
+		reserved: 2000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool2 := &testPool{
+		name:     "pool-2",
+		reserved: 1000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool3 := &testPool{
+		name:     "pool-3",
+		reserved: 0,
+		usage: filesystem.Usage{
+			Size: 100000,
+			Used: 0,
+		},
+		ptype: pkg.HDDDevice,
+	}
+
+	mod := Module{
+		pools: []filesystem.Pool{
+			pool1, pool2, pool3,
+		},
+	}
+
+	sub := &testVolume{
+		name: vdiskVolumeName,
+	}
+
+	pool3.On("AddVolume", vdiskVolumeName).Return(sub, nil)
+
+	pool1.On("Volumes").Return([]filesystem.Volume{sub}, nil)
+	pool2.On("Volumes").Return([]filesystem.Volume{}, nil)
+	pool3.On("Volumes").Return([]filesystem.Volume{}, nil)
+
+	_, err := mod.VDiskFindCandidate(10000)
+	require.EqualError(err, "Not enough space left in pools of this type ssd")
+
+}
+
+func TestVDiskFindCandidatesNoSpace(t *testing.T) {
+	require := require.New(t)
+
+	pool1 := &testPool{
+		name:     "pool-1",
+		reserved: 2000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool2 := &testPool{
+		name:     "pool-2",
+		reserved: 1000,
+		usage: filesystem.Usage{
+			Size: 10000,
+			Used: 100,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	pool3 := &testPool{
+		name:     "pool-3",
+		reserved: 0,
+		usage: filesystem.Usage{
+			Size: 100000,
+			Used: 0,
+		},
+		ptype: pkg.SSDDevice,
+	}
+
+	mod := Module{
+		pools: []filesystem.Pool{
+			pool1, pool2, pool3,
+		},
+	}
+
+	sub := &testVolume{
+		name: vdiskVolumeName,
+	}
+
+	pool3.On("AddVolume", vdiskVolumeName).Return(sub, nil)
+
+	pool1.On("Volumes").Return([]filesystem.Volume{sub}, nil)
+	pool2.On("Volumes").Return([]filesystem.Volume{}, nil)
+	pool3.On("Volumes").Return([]filesystem.Volume{}, nil)
+
+	_, err := mod.VDiskFindCandidate(10000)
+	require.NoError(err)
+
+	if ok := pool3.AssertCalled(t, "AddVolume", vdiskVolumeName); !ok {
+		t.Fail()
+	}
+}
diff --git a/pkg/upgrade/hub_test.go b/pkg/upgrade/hub_test.go
index 3839682a..99169409 100644
--- a/pkg/upgrade/hub_test.go
+++ b/pkg/upgrade/hub_test.go
@@ -29,7 +29,7 @@ func TestInfo(t *testing.T) {
 	for _, tc := range tt {
 		t.Run(tc.name, func(t *testing.T) {
 
-			var info flistInfo
+			var info FullFListInfo
 			err := json.Unmarshal([]byte(tc.input), &info)
 			require.NoError(t, err)
 
@@ -44,7 +44,7 @@ func TestInfo(t *testing.T) {
 func TestInfoGet(t *testing.T) {
 	const flist = "tf-zos/zos:0.1.0-rc1.flist"
 
-	var hub hubClient
+	var hub HubClient
 	info, err := hub.Info(flist)
 	require.NoError(t, err)
 	require.Equal(t, flist, info.Absolute())
diff --git a/pkg/upgrade/upgrade_test.go b/pkg/upgrade/upgrade_test.go
index 8088f98b..9b841903 100644
--- a/pkg/upgrade/upgrade_test.go
+++ b/pkg/upgrade/upgrade_test.go
@@ -1,1 +1,50 @@
 package upgrade
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/threefoldtech/0-fs/meta"
+)
+
+func TestUpgraderDownload(t *testing.T) {
+	require := require.New(t)
+
+	up := &Upgrader{
+		cache: "/tmp/zfs-test-cache",
+	}
+
+	err := Storage(defaultHubStorage)(up)
+	require.NoError(err)
+
+	const flist = "thabet/redis.flist"
+
+	store, err := up.getFlist(flist)
+	require.NoError(err)
+	tmp, err := ioutil.TempDir("", "download-*")
+
+	require.NoError(err)
+	defer os.RemoveAll(tmp)
+
+	err = up.copyRecursive(store, tmp)
+	require.NoError(err)
+
+	// validation of download
+	err = store.Walk("", func(path string, info meta.Meta) error {
+		downloaded := filepath.Join(tmp, path)
+		stat, err := os.Stat(downloaded)
+		require.NoError(err)
+		require.Equal(info.IsDir(), stat.IsDir())
+		if info.IsDir() {
+			return nil
+		}
+
+		require.Equal(info.Info().Size, uint64(stat.Size()))
+		return nil
+	})
+
+	require.NoError(err)
+}