Repository: threefoldtech/zos
Pull request #452 (2019-12-19 14:24:40)

revert testnet explorer address to proper value


Patch:
diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml
index 13b97571..346cd218 100644
--- a/.github/workflows/publish-release.yaml
+++ b/.github/workflows/publish-release.yaml
@@ -48,7 +48,7 @@ jobs:
         user: tf-autobuilder
         root: archive
         token: ${{ secrets.HUB_JWT }}
-        name: zos-${{ github.ref }}.flist
+        name: zos:${{ github.ref }}.flist
     
     - name: Rename flist from zos-${{ github.ref }}.flist to zos:${{ github.ref }}.flist
       if: success()
diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go
index 87e4c4b5..7529ae4c 100644
--- a/cmds/identityd/main.go
+++ b/cmds/identityd/main.go
@@ -188,7 +188,7 @@ func main() {
 	}()
 
 	utils.OnDone(ctx, func(_ error) {
-		log.Info().Msg("received a termination signal")
+		log.Info().Msg("shutting down")
 	})
 
 	if bootMethod != upgrade.BootMethodFList {
@@ -233,17 +233,10 @@ func debugReinstall(boot *upgrade.Boot, up *upgrade.Upgrader) {
 				continue
 			}
 
-			if err := Safe(func() error {
-				return up.Upgrade(current, current)
-			}); err != nil {
-				log.Error().Err(err).Msg("reinstall failed")
-			} else {
-				log.Info().Msg("reinstall completed successfully")
-			}
+			up.Upgrade(current, current)
 		}
 	}()
 }
-
 func upgradeLoop(
 	ctx context.Context,
 	boot *upgrade.Boot,
diff --git a/cmds/networkd/main.go b/cmds/networkd/main.go
index 9cbab1ee..1dc63c39 100644
--- a/cmds/networkd/main.go
+++ b/cmds/networkd/main.go
@@ -3,7 +3,6 @@ package main
 import (
 	"context"
 	"flag"
-	"fmt"
 	"os"
 	"time"
 
@@ -84,10 +83,6 @@ func main() {
 		ifaceVersion = exitIface.Version
 	}
 
-	if err := ndmz.Create(nodeID); err != nil {
-		log.Fatal().Err(err).Msgf("failed to create DMZ")
-	}
-
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
@@ -96,33 +91,17 @@ func main() {
 		for {
 			select {
 			case iface := <-ch:
-
-				// When changing public IP configuration, we need to also update how the NDMZ interfaces are plumbed together
-				// to achieve this any time a new public config is received, we first delete the NDMZ namespace
-				// create/update the public namespace and then re-create the NDMZ
-
-				log.Info().Str("config", fmt.Sprintf("%+v", iface)).Msg("public IP configuration received")
-
-				if err := ndmz.Delete(); err != nil {
-					log.Error().Err(err).Msg("error deleting ndmz during public ip configuration")
-					continue
-				}
-
-				if err = configurePubIface(iface, nodeID); err != nil {
-					log.Error().Err(err).Msg("error configuring public IP")
-					continue
-				}
-
-				if err := ndmz.Create(nodeID); err != nil {
-					log.Error().Err(err).Msg("error configuring ndmz during public ip configuration")
-					continue
-				}
+				_ = configurePubIface(iface, nodeID)
 			case <-ctx.Done():
 				return
 			}
 		}
 	}(ctx, chIface)
 
+	if err := ndmz.Create(nodeID); err != nil {
+		log.Fatal().Err(err).Msgf("failed to create DMZ")
+	}
+
 	if err := startServer(ctx, broker, networker); err != nil {
 		log.Fatal().Err(err).Msg("unexpected error")
 	}
diff --git a/pkg/environment/environment.go b/pkg/environment/environment.go
index 279bff06..8d3c8add 100644
--- a/pkg/environment/environment.go
+++ b/pkg/environment/environment.go
@@ -50,8 +50,7 @@ var (
 
 	envTest = Environment{
 		RunningMode: RunningTest,
-		// BcdbURL:     "tcp://explorer.testnet.grid.tf:8901",
-		BcdbURL: "tcp://0.tcp.ngrok.io:14669",
+		BcdbURL:     "tcp://explorer.testnet.grid.tf:8901",
 		// ProvisionTimeout:  120,
 		// ProvisionInterval: 10,
 	}
diff --git a/pkg/gedis/commands_provision.go b/pkg/gedis/commands_provision.go
index 9046040b..3f8e3979 100644
--- a/pkg/gedis/commands_provision.go
+++ b/pkg/gedis/commands_provision.go
@@ -56,7 +56,7 @@ func (g *Gedis) Reserve(r *provision.Reservation, nodeID pkg.Identifier) (string
 		}
 	}
 
-	result, err := Bytes(g.Send("tfgrid.workloads.workload_manager", "reservation_register", Args{
+	result, err := Bytes(g.Send("tfgrid.directory.workload_manager", "reservation_register", Args{
 		"reservation": res,
 	}))
 
@@ -74,7 +74,7 @@ func (g *Gedis) Reserve(r *provision.Reservation, nodeID pkg.Identifier) (string
 
 // Get implements provision.ReservationGetter
 func (g *Gedis) Get(id string) (*provision.Reservation, error) {
-	result, err := Bytes(g.Send("tfgrid.workloads.workload_manager", "workload_get", Args{
+	result, err := Bytes(g.Send("tfgrid.directory.workload_manager", "workload_get", Args{
 		"gwid": id,
 	}))
 
@@ -97,7 +97,7 @@ func (g *Gedis) Get(id string) (*provision.Reservation, error) {
 // to get the reservation part.
 func (g *Gedis) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservation, error) {
 
-	result, err := Bytes(g.Send("tfgrid.workloads.workload_manager", "workloads_list", Args{
+	result, err := Bytes(g.Send("tfgrid.directory.workload_manager", "workloads_list", Args{
 		"node_id": nodeID.Identity(),
 		"cursor":  from,
 	}))
@@ -165,7 +165,7 @@ func (g *Gedis) Feedback(id string, r *provision.Result) error {
 		Epoch:      schema.Date{r.Created},
 	}
 
-	_, err := g.Send("tfgrid.workloads.workload_manager", "set_workload_result", Args{
+	_, err := g.Send("tfgrid.directory.workload_manager", "set_workload_result", Args{
 		"global_workload_id": id,
 		"result":             result,
 	})
diff --git a/pkg/network.go b/pkg/network.go
index f6200d7e..e6ebb9d2 100644
--- a/pkg/network.go
+++ b/pkg/network.go
@@ -37,8 +37,7 @@ type Networker interface {
 	// ZDBPrepare creates a network namespace with a macvlan interface into it
 	// to allow the 0-db container to be publicly accessible
 	// it retusn the name of the network namespace created
-	// hw is an optional hardware address that will be set on the new interface
-	ZDBPrepare(hw net.HardwareAddr) (string, error)
+	ZDBPrepare() (string, error)
 
 	// Addrs return the IP addresses of interface
 	// if the interface is in a network namespace netns needs to be not empty
diff --git a/pkg/network/macvlan/macvlan.go b/pkg/network/macvlan/macvlan.go
index a750cc56..0e2e56ec 100644
--- a/pkg/network/macvlan/macvlan.go
+++ b/pkg/network/macvlan/macvlan.go
@@ -87,14 +87,8 @@ func Create(name string, master string, netns ns.NetNS) (*netlink.Macvlan, error
 }
 
 // Install configures a macvlan interfaces created with Create method
-func Install(link *netlink.Macvlan, hw net.HardwareAddr, ips []*net.IPNet, routes []*netlink.Route, netns ns.NetNS) error {
+func Install(link *netlink.Macvlan, ips []*net.IPNet, routes []*netlink.Route, netns ns.NetNS) error {
 	return netns.Do(func(_ ns.NetNS) error {
-		if hw != nil && len(hw) != 0 {
-			if err := netlink.LinkSetHardwareAddr(link, hw); err != nil {
-				return err
-			}
-		}
-
 		name := link.Attrs().Name
 
 		for _, ip := range ips {
diff --git a/pkg/network/ndmz/ndmz.go b/pkg/network/ndmz/ndmz.go
index ae490c33..4b6e0906 100644
--- a/pkg/network/ndmz/ndmz.go
+++ b/pkg/network/ndmz/ndmz.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/threefoldtech/zos/pkg"
 	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
-	"github.com/threefoldtech/zos/pkg/network/types"
 
 	"github.com/threefoldtech/zos/pkg/network/nr"
 
@@ -68,23 +67,42 @@ func Create(nodeID pkg.Identifier) error {
 		return err
 	}
 
-	if err := createMacVlan(netNS); err != nil {
+	if namespace.Exists("public") {
+		err = createMacVlan(netNS)
+	} else {
+		err = attachVeth(netNS)
+	}
+	if err != nil {
 		return err
 	}
 
 	// set mac address to something static to make sure we receive the same IP from a DHCP server
-	mac := ifaceutil.HardwareAddrFromInputBytes([]byte(nodeID.Identity() + ndmzNsMACDerivationSuffix))
+	pubiface, err := getPublicIface()
+	if err != nil {
+		return err
+	}
+	mac, err := ifaceutil.GetMAC(pubiface, nil)
+	if err != nil {
+		return err
+	}
+
+	log.Debug().
+		Str("iface", pubiface).
+		Str("mac", mac.String()).
+		Msg("public iface found")
+
+	mac = ifaceutil.HardwareAddrFromInputBytes([]byte(nodeID.Identity() + ndmzNsMACDerivationSuffix))
 	log.Debug().
 		Str("mac", mac.String()).
 		Msg("set mac on public iface")
 
-	if err = ifaceutil.SetMAC(types.PublicIface, mac, netNS); err != nil {
+	if err = ifaceutil.SetMAC("public", mac, netNS); err != nil {
 		return err
 	}
 
 	err = netNS.Do(func(_ ns.NetNS) error {
 		// run DHCP to interface public in ndmz
-		received, err := dhcp.Probe(types.PublicIface)
+		received, err := dhcp.Probe("public")
 		if err != nil {
 			return err
 		}
@@ -100,36 +118,53 @@ func Create(nodeID pkg.Identifier) error {
 	return applyFirewall()
 }
 
-// Delete deletes the NDMZ network namespace
-func Delete() error {
-	netNS, err := namespace.GetByName(netNSNDMZ)
-	if err == nil {
-		if err := namespace.Delete(netNS); err != nil {
-			return errors.Wrap(err, "failed to delete ndmz network namespace")
+func createMacVlan(netNS ns.NetNS) error {
+	if !macvlan.Exists("public", netNS) {
+		pubIface, err := getPublicIface()
+		if err != nil {
+			return err
+		}
+
+		_, err = macvlan.Create("public", pubIface, netNS)
+		if err != nil {
+			return err
 		}
 	}
 
 	return nil
 }
 
-func createMacVlan(netNS ns.NetNS) error {
-	if !macvlan.Exists(types.PublicIface, netNS) {
+func attachVeth(netNS ns.NetNS) error {
 
-		var (
-			master netlink.Link
-			err    error
-		)
+	const (
+		vethHost = "to-dmz"
+		vethNDMZ = "public"
+	)
 
-		if namespace.Exists(types.PublicNamespace) {
-			master, err = getPublicIface()
-		} else {
-			master, err = netlink.LinkByName("zos")
+	if !ifaceutil.Exists(vethHost, nil) || !ifaceutil.Exists(vethNDMZ, netNS) {
+		if _, _, err := ip.SetupVethWithName(vethHost, vethNDMZ, 1500, netNS); err != nil {
+			return errors.Wrap(err, "failed to create veth pair to connect zos bridge and ndmz bridge")
 		}
-		if err != nil {
-			return err
+		if _, err := sysctl.Sysctl(fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6", vethHost), "1"); err != nil {
+			return errors.Wrapf(err, "failed to disable ip6 on interface %s", vethHost)
 		}
+	}
+	log.Info().
+		Str("ndmz side", vethNDMZ).
+		Str("host side", vethHost).
+		Msg("veth pair for ndmz to public zos bridge created")
+
+	lVethBr, err := netlink.LinkByName(vethHost)
+	if err != nil {
+		return err
+	}
 
-		_, err = macvlan.Create(types.PublicIface, master.Attrs().Name, netNS)
+	br, err := bridge.Get("zos") //TODO: use constant
+	if err != nil {
+		return err
+	}
+
+	if err := bridge.AttachNic(lVethBr, br); err != nil {
 		return err
 	}
 
@@ -137,20 +172,13 @@ func createMacVlan(netNS ns.NetNS) error {
 }
 
 func createRoutingBridge(netNS ns.NetNS) error {
-	if bridge.Exists(BridgeNDMZ) && namespace.Exists(netNSNDMZ) {
+	if bridge.Exists(BridgeNDMZ) {
 		return nil
 	}
 
-	var (
-		br  *netlink.Bridge
-		err error
-	)
-
-	if !bridge.Exists(BridgeNDMZ) {
-		br, err = bridge.New(BridgeNDMZ)
-		if err != nil {
-			return err
-		}
+	br, err := bridge.New(BridgeNDMZ)
+	if err != nil {
+		return err
 	}
 
 	vethNDMZ := "tonrs"
@@ -289,3 +317,225 @@ func AttachNR(networkID string, nr *nr.NetResource) error {
 	}
 	return nil
 }
+
+// AddNetResource adds the routes of a network resource to the gateway network namespace
+// func (gw *NDMZ) AddNetResource(netRes *nr.NetResource) error {
+// 	log.Info().Msg("add network resource to gateway namespace")
+// 	gwNS, err := namespace.GetByName(types.GatewayNamespace)
+// 	if err != nil {
+// 		return errors.Wrap(err, "gateway namespace not found")
+// 	}
+// 	defer gwNS.Close()
+
+// 	netResNS, err := namespace.GetByName(netRes.NamespaceName())
+// 	if err != nil {
+// 		return errors.Wrapf(err, "namespace %s not found", netRes.NamespaceName())
+// 	}
+// 	defer netResNS.Close()
+
+// 	if err := gw.configNRIPv6(netRes, gwNS, netResNS); err != nil {
+// 		return err
+// 	}
+
+// 	if err := gw.configNRIPv4(netRes, gwNS, netResNS); err != nil {
+// 		return err
+// 	}
+
+// 	return nil
+// }
+
+// func (gw *NDMZ) configNRIPv6(netRes *nr.NetResource, gwNS, netResNS ns.NetNS) error {
+
+// 	epName := netRes.Nibble().EPPubName()
+// 	gwName := netRes.Nibble().GWtoEPName()
+
+// 	if !ifaceutil.Exists(epName, netResNS) || !ifaceutil.Exists(gwName, gwNS) {
+// 		log.Warn().Msg("one side of the gateway veth pair does not exists, deleting both")
+// 		if err := ifaceutil.Delete(epName, netResNS); err != nil {
+// 			return err
+// 		}
+// 		if err := ifaceutil.Delete(gwName, gwNS); err != nil {
+// 			return err
+// 		}
+
+// 		log.Info().
+// 			Str("gateway side", gwName).
+// 			Str("exit point side", epName).
+// 			Msg("create a veth pair in the host namespace and send one side into the gateway namespace")
+// 		if _, _, err := ip.SetupVethWithName(epName, gwName, 1500, gwNS); err != nil {
+// 			return errors.Wrap(err, "failed to create veth pair for gateway namespace")
+// 		}
+// 		log.Info().
+// 			Str("gwVeth", gwName).
+// 			Str("epVeth", epName).
+// 			Msg("veth pair for gateway and exit point created")
+
+// 		// send the other side inside in the exit point network resource namespace
+// 		EPLink, err := netlink.LinkByName(epName)
+// 		if err != nil {
+// 			return errors.Wrapf(err, "failed to get interface %s", epName)
+// 		}
+// 		if err = netlink.LinkSetNsFd(EPLink, int(netResNS.Fd())); err != nil {
+// 			return fmt.Errorf("failed to move interface %s to network resource netns: %v", epName, err)
+// 		}
+// 	}
+
+// 	err := gwNS.Do(func(_ ns.NetNS) error {
+// 		GWLink, err := netlink.LinkByName(gwName)
+// 		if err != nil {
+// 			return errors.Wrapf(err, "failed to get interface %s in gateway namespace", gwName)
+// 		}
+
+// 		addr := &netlink.Addr{IPNet: netRes.Nibble().GWtoEPLL(), Label: ""}
+// 		if err := netlink.AddrAdd(GWLink, addr); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+
+// 		route := netRes.Nibble().GWDefaultRoute()
+// 		route.LinkIndex = GWLink.Attrs().Index
+// 		if err := netlink.RouteAdd(route); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+
+// 		routes, err := netRes.GWTNRoutesIPv6()
+// 		if err != nil {
+// 			return err
+// 		}
+// 		for _, route := range routes {
+// 			route.LinkIndex = GWLink.Attrs().Index
+// 			if err := netlink.RouteAdd(route); err != nil && !os.IsExist(err) {
+// 				return errors.Wrapf(err, "failed to set route %s on %s", route.String(), GWLink.Attrs().Name)
+// 			}
+// 		}
+
+// 		return nil
+// 	})
+// 	if err != nil {
+// 		return errors.Wrap(err, "failed to configure veth pair end in gateway namespace")
+// 	}
+
+// 	// configure veth pair inside the exit point network resource namespace
+// 	err = netResNS.Do(func(_ ns.NetNS) error {
+// 		EPLink, err := netlink.LinkByName(epName)
+// 		if err != nil {
+// 			return errors.Wrapf(err, "failed to get interface %s in exit point namespace", epName)
+// 		}
+
+// 		addr := &netlink.Addr{IPNet: netRes.Nibble().EPPubLL(), Label: ""}
+// 		if err := netlink.AddrAdd(EPLink, addr); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+
+// 		if err := netlink.LinkSetUp(EPLink); err != nil {
+// 			return err
+// 		}
+
+// 		route := netRes.Nibble().NRDefaultRoute()
+// 		route.LinkIndex = EPLink.Attrs().Index
+// 		if err := netlink.RouteAdd(route); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+// 		return nil
+// 	})
+// 	if err != nil {
+// 		return errors.Wrap(err, "failed to configure veth pair end in exit point namespace")
+// 	}
+// 	return nil
+// }
+
+// func (gw *NDMZ) configNRIPv4(netRes *nr.NetResource, gwNS, netResNS ns.NetNS) error {
+// 	ep4pubName := netRes.Nibble().EP4PubName()
+// 	br4pubName := netRes.Nibble().Br4PubName()
+
+// 	if !ifaceutil.Exists(ep4pubName, netResNS) || !ifaceutil.Exists(br4pubName, nil) {
+// 		log.Warn().Msg("one side of the gateway veth pair does not exists, deleting both")
+// 		if err := ifaceutil.Delete(ep4pubName, netResNS); err != nil {
+// 			return err
+// 		}
+// 		if err := ifaceutil.Delete(br4pubName, nil); err != nil {
+// 			return err
+// 		}
+
+// 		if _, _, err := ip.SetupVethWithName(br4pubName, ep4pubName, 1500, netResNS); err != nil {
+// 			return errors.Wrap(err, "failed to create veth pair for bridge gateway")
+// 		}
+// 		log.Info().
+// 			Str("gatway side", br4pubName).
+// 			Str("bridge side", ep4pubName).
+// 			Msg("veth pair for ipv4 gateway bridge created")
+// 	}
+
+// 	br, err := bridge.Get(BridgeNDMZ)
+// 	if err != nil {
+// 		return err
+// 	}
+
+// 	lVethBr, err := netlink.LinkByName(br4pubName)
+// 	if err != nil {
+// 		return err
+// 	}
+
+// 	if err := bridge.AttachNic(lVethBr, br); err != nil {
+// 		return err
+// 	}
+
+// 	if err := netResNS.Do(func(_ ns.NetNS) error {
+// 		lep4pub, err := netlink.LinkByName(ep4pubName)
+// 		if err != nil {
+// 			return err
+// 		}
+
+// 		if _, err := sysctl.Sysctl(fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6", ep4pubName), "1"); err != nil {
+// 			return err
+// 		}
+
+// 		addr := &netlink.Addr{IPNet: netRes.Nibble().WGIP4()}
+// 		log.Debug().Msgf("set addr %s on %s", addr.IPNet.String(), ep4pubName)
+// 		if err := netlink.AddrAdd(lep4pub, addr); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+
+// 		if err := netlink.LinkSetUp(lep4pub); err != nil {
+// 			return err
+// 		}
+
+// 		route := &netlink.Route{
+// 			Dst: &net.IPNet{
+// 				IP:   net.ParseIP("0.0.0.0"),
+// 				Mask: net.CIDRMask(0, 32),
+// 			},
+// 			Gw:        net.ParseIP("10.1.0.1"),
+// 			LinkIndex: lep4pub.Attrs().Index,
+// 		}
+
+// 		log.Debug().Msgf("set route %s on %s %d", route.String(), ep4pubName, lep4pub.Attrs().Index)
+// 		if err := netlink.RouteAdd(route); err != nil && !os.IsExist(err) {
+// 			return err
+// 		}
+// 		return nil
+// 	}); err != nil {
+// 		return err
+// 	}
+
+// 	return gwNS.Do(func(_ ns.NetNS) error {
+// 		l, err := netlink.LinkByName(vethGWSide)
+// 		if err != nil {
+// 			return err
+// 		}
+
+// 		routes, err := netRes.GWTNRoutesIPv4()
+// 		if err != nil {
+// 			return err
+// 		}
+
+// 		for _, route := range routes {
+// 			route.LinkIndex = l.Attrs().Index
+// 			if err := netlink.RouteAdd(route); err != nil && !os.IsExist(err) {
+// 				return errors.Wrapf(err, "failed to set route %s on %s", route.String(), l.Attrs().Name)
+// 			}
+// 		}
+
+// 		// TODO: add IPv4 public IP once farmer can specify ipv4 public address in the farm management
+// 		return nil
+// 	})
+// }
diff --git a/pkg/network/ndmz/public.go b/pkg/network/ndmz/public.go
index 33bf16ee..6744def5 100644
--- a/pkg/network/ndmz/public.go
+++ b/pkg/network/ndmz/public.go
@@ -11,8 +11,8 @@ import (
 	"github.com/vishvananda/netlink"
 )
 
-func getPublicIface() (netlink.Link, error) {
-	var iface netlink.Link
+func getPublicIface() (string, error) {
+	var ifaceName string
 
 	pubNS, err := namespace.GetByName(types.PublicNamespace)
 
@@ -31,36 +31,36 @@ func getPublicIface() (netlink.Link, error) {
 			ifaceIndex = public.Attrs().ParentIndex
 			return nil
 		}); err != nil {
-			return nil, err
+			return "", err
 		}
 
 		master, err := netlink.LinkByIndex(ifaceIndex)
 		if err != nil {
-			return nil, errors.Wrapf(err, "failed to get link by index %d", ifaceIndex)
+			return "", errors.Wrapf(err, "failed to get link by index %d", ifaceIndex)
 		}
-		iface = master
+		ifaceName = master.Attrs().Name
 	} else {
 		zos, err := netlink.LinkByName("zos")
 		if err != nil {
-			return nil, errors.Wrap(err, "failed to get zos link")
+			return "", errors.Wrap(err, "failed to get zos link")
 		}
 		// find the name of the interface attached to zos bridge
 		links, err := netlink.LinkList()
 		if err != nil {
-			return nil, errors.Wrap(err, "failed to list interfaces")
+			return "", errors.Wrap(err, "failed to list interfaces")
 		}
 		for _, link := range links {
 			if link.Attrs().MasterIndex == zos.Attrs().Index && link.Type() == "device" {
-				iface = link
+				ifaceName = link.Attrs().Name
 				break
 			}
 		}
 	}
 
-	if iface == nil {
-		return nil, fmt.Errorf("not interface with default gateway found")
+	if ifaceName == "" {
+		return "", fmt.Errorf("not interface with default gateway found")
 	}
 
-	log.Info().Str("iface", iface.Attrs().Name).Msg("interface with default gateway found")
-	return iface, nil
+	log.Info().Str("iface", ifaceName).Msg("interface with default gateway found")
+	return ifaceName, nil
 }
diff --git a/pkg/network/networker.go b/pkg/network/networker.go
index be89964e..c64b318d 100644
--- a/pkg/network/networker.go
+++ b/pkg/network/networker.go
@@ -184,7 +184,8 @@ func (n *networker) Leave(networkdID pkg.NetID, containerID string) error {
 
 // ZDBPrepare sends a macvlan interface into the
 // network namespace of a ZDB container
-func (n networker) ZDBPrepare(hw net.HardwareAddr) (string, error) {
+func (n networker) ZDBPrepare() (string, error) {
+
 	netNSName, err := ifaceutil.RandomName("zdb-ns-")
 	if err != nil {
 		return "", err
@@ -211,9 +212,8 @@ func (n networker) ZDBPrepare(hw net.HardwareAddr) (string, error) {
 		return "", errors.Wrap(err, "failed to create public mac vlan interface")
 	}
 
-	log.Debug().Str("HW", hw.String()).Str("macvlan", macVlan.Name).Msg("setting hw address on link")
 	// we don't set any route or ip
-	if err := macvlan.Install(macVlan, hw, []*net.IPNet{}, []*netlink.Route{}, netNs); err != nil {
+	if err := macvlan.Install(macVlan, []*net.IPNet{}, []*netlink.Route{}, netNs); err != nil {
 		return "", err
 	}
 
diff --git a/pkg/network/public.go b/pkg/network/public.go
index 97be26b4..7caa229c 100644
--- a/pkg/network/public.go
+++ b/pkg/network/public.go
@@ -25,7 +25,6 @@ func CreatePublicNS(iface *types.PubIface, nodeID pkg.Identifier) error {
 		pubNS    ns.NetNS
 		pubIface *netlink.Macvlan
 		err      error
-		mac      net.HardwareAddr
 	)
 
 	if !namespace.Exists(types.PublicNamespace) {
@@ -44,7 +43,11 @@ func CreatePublicNS(iface *types.PubIface, nodeID pkg.Identifier) error {
 			if err != nil {
 				return errors.Wrap(err, "failed to create public mac vlan interface")
 			}
-			mac = ifaceutil.HardwareAddrFromInputBytes([]byte(nodeID.Identity() + publicNsMACDerivationSuffix))
+			mac := ifaceutil.HardwareAddrFromInputBytes([]byte(nodeID.Identity() + publicNsMACDerivationSuffix))
+			if err = ifaceutil.SetMAC(types.PublicIface, mac, pubNS); err != nil {
+				return errors.Wrap(err, "failed to set mac address on public mac vlan interface")
+			}
+			log.Debug().Str("mac", mac.String()).Msg("Set mac address on public mac vlan interface")
 		default:
 			return fmt.Errorf("unsupported public interface type %s", iface.Type)
 		}
@@ -102,7 +105,7 @@ func CreatePublicNS(iface *types.PubIface, nodeID pkg.Identifier) error {
 		return err
 	}
 
-	if err := macvlan.Install(pubIface, mac, ips, routes, pubNS); err != nil {
+	if err := macvlan.Install(pubIface, ips, routes, pubNS); err != nil {
 		return err
 	}
 
diff --git a/pkg/provision/zdb.go b/pkg/provision/zdb.go
index 864bc3ff..7a0c9f23 100644
--- a/pkg/provision/zdb.go
+++ b/pkg/provision/zdb.go
@@ -11,7 +11,6 @@ import (
 	"time"
 
 	"github.com/cenkalti/backoff/v3"
-	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
 	"github.com/threefoldtech/zos/pkg/zdb"
 
 	"github.com/pkg/errors"
@@ -134,8 +133,6 @@ func createZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg
 		slog = log.With().Str("containerID", string(name)).Logger()
 	)
 
-	hw := ifaceutil.HardwareAddrFromInputBytes([]byte(allocation.VolumeID))
-
 	slog.Debug().Str("flist", zdbFlistURL).Msg("mounting flist")
 	rootFS, err := flist.Mount(zdbFlistURL, "", pkg.MountOptions{
 		Limit:    10,
@@ -156,7 +153,7 @@ func createZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg
 	}
 
 	// create the network namespace and macvlan for the 0-db container
-	netNsName, err := network.ZDBPrepare(hw)
+	netNsName, err := network.ZDBPrepare()
 	if err != nil {
 		if err := flist.Umount(rootFS); err != nil {
 			slog.Error().Err(err).Str("path", rootFS).Msgf("failed to unmount")
diff --git a/pkg/storage.go b/pkg/storage.go
index f9afb6d9..7ca4dc62 100644
--- a/pkg/storage.go
+++ b/pkg/storage.go
@@ -35,26 +35,6 @@ func (e ErrNotEnoughSpace) Error() string {
 // i.e. SSD or HDD
 type DeviceType string
 
-type (
-	// BrokenDevice is a disk which is somehow not fully functional. Storage keeps
-	// track of disks which have failed at some point, so they are not used, and
-	// to be able to later report this to other daemons.
-	BrokenDevice struct {
-		// Path to allow identification of the disk
-		Path string
-		// Err returned which lead to the disk being marked as faulty
-		Err error
-	}
-
-	// BrokenPool contains info about a malfunctioning storage pool
-	BrokenPool struct {
-		// Label of the broken pool
-		Label string
-		// Err returned by the action which let to the pool being marked as broken
-		Err error
-	}
-)
-
 // Known device types
 const (
 	SSDDevice DeviceType = "SSD"
@@ -130,8 +110,4 @@ type StorageModule interface {
 
 	// Total gives the total amount of storage available for a device type
 	Total(kind DeviceType) (uint64, error)
-	// BrokenPools lists the broken storage pools that have been detected
-	BrokenPools() []BrokenPool
-	// BrokenDevices lists the broken devices that have been detected
-	BrokenDevices() []BrokenDevice
 }
diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go
index 2b90777b..5aa0fcb9 100644
--- a/pkg/storage/storage.go
+++ b/pkg/storage/storage.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"sort"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/google/uuid"
@@ -30,12 +29,8 @@ var (
 )
 
 type storageModule struct {
-	volumes       []filesystem.Pool
-	brokenPools   []pkg.BrokenPool
-	devices       filesystem.DeviceManager
-	brokenDevices []pkg.BrokenDevice
-
-	mu sync.RWMutex
+	volumes []filesystem.Pool
+	devices filesystem.DeviceManager
 }
 
 // New create a new storage module service
@@ -51,10 +46,8 @@ func New() (pkg.StorageModule, error) {
 	}
 
 	s := &storageModule{
-		volumes:       []filesystem.Pool{},
-		brokenPools:   []pkg.BrokenPool{},
-		devices:       m,
-		brokenDevices: []pkg.BrokenDevice{},
+		volumes: []filesystem.Pool{},
+		devices: m,
 	}
 
 	// go for a simple linear setup right now
@@ -77,8 +70,6 @@ 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) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
 	var total uint64
 
 	for idx := range s.volumes {
@@ -98,20 +89,6 @@ func (s *storageModule) Total(kind pkg.DeviceType) (uint64, error) {
 	return total, nil
 }
 
-// BrokenPools lists the broken storage pools that have been detected
-func (s *storageModule) 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 {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	return s.brokenDevices
-}
-
 /**
 initialize, must be called at least onetime each boot.
 What Initialize will do is the following:
@@ -120,10 +97,6 @@ What Initialize will do is the following:
  - If new pools were created, the pool is going to be mounted automatically
 **/
 func (s *storageModule) 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()
-	defer s.mu.Unlock()
 	log.Info().Msgf("Initializing storage module")
 
 	// Make sure we finish in 1 minute
@@ -143,22 +116,16 @@ func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
 	for _, volume := range existingPools {
 		if _, mounted := volume.Mounted(); mounted {
 			log.Debug().Msgf("Volume %s already mounted", volume.Name())
-			// volume is already mounted, skip mounting it again, make sure it is
-			// in the list of available pools. Since we are in the initialize method,
-			// we can safely assume the pool has not been added before, so no need
-			// to check for duplicate entries.
-			s.volumes = append(s.volumes, volume)
+			// volume is already mounted, skip mounting it again
 			continue
 		}
 		_, err = volume.Mount()
 		if err != nil {
-			s.brokenPools = append(s.brokenPools, pkg.BrokenPool{Label: volume.Name(), Err: err})
-			log.Warn().Msgf("Failed to mount volume %v", volume.Name())
-			continue
+			return err
 		}
 		log.Debug().Msgf("Mounted volume %s", volume.Name())
-		s.volumes = append(s.volumes, volume)
 	}
+	s.volumes = append(s.volumes, existingPools...)
 
 	// list disks
 	log.Info().Msgf("Finding free disks")
@@ -225,14 +192,7 @@ func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
 
 			pool, err := fs.Create(ctx, uuid.New().String(), policy.Raid, poolDevices...)
 			if err != nil {
-				// Failure to create a filesystem -> disk is dead. It is possible
-				// that multiple devices are used to create a single pool, and only
-				// one devices is actuall broken. We should probably expand on
-				// this once we start to use storagepools spanning multiple disks.
-				for _, dev := range poolDevices {
-					s.brokenDevices = append(s.brokenDevices, pkg.BrokenDevice{Path: dev.Path, Err: err})
-				}
-				continue
+				return err
 			}
 
 			newPools = append(newPools, pool)
@@ -246,17 +206,18 @@ func (s *storageModule) initialize(policy pkg.StoragePolicy) error {
 		if _, mounted := newPools[idx].Mounted(); !mounted {
 			log.Debug().Msgf("Mounting volume %s", newPools[idx].Name())
 			if _, err = newPools[idx].Mount(); err != nil {
-				s.brokenPools = append(s.brokenPools, pkg.BrokenPool{Label: newPools[idx].Name(), Err: err})
-				continue
+				return err
 			}
-			s.volumes = append(s.volumes, newPools[idx])
 		}
 	}
 
+	s.volumes = append(s.volumes, newPools...)
+
 	return s.ensureCache()
 }
 
 func (s *storageModule) Maintenance() error {
+
 	for _, pool := range s.volumes {
 		log.Info().
 			Str("pool", pool.Name()).
@@ -361,6 +322,7 @@ func (s *storageModule) ensureCache() error {
 		fs, err := s.createSubvol(cacheSize, cacheLabel, pkg.SSDDevice)
 		if errors.Is(err, pkg.ErrNotEnoughSpace{}) {
 			// No space on SSD (probably no SSD in the node at all), try HDD
+			err = nil
 			fs, err = s.createSubvol(cacheSize, cacheLabel, pkg.HDDDevice)
 		}
 		if err != nil {
diff --git a/pkg/stubs/network_stub.go b/pkg/stubs/network_stub.go
index 68e6c0cd..cf9adf13 100644
--- a/pkg/stubs/network_stub.go
+++ b/pkg/stubs/network_stub.go
@@ -96,8 +96,8 @@ func (s *NetworkerStub) Leave(arg0 pkg.NetID, arg1 string) (ret0 error) {
 	return
 }
 
-func (s *NetworkerStub) ZDBPrepare(arg0 []uint8) (ret0 string, ret1 error) {
-	args := []interface{}{arg0}
+func (s *NetworkerStub) ZDBPrepare() (ret0 string, ret1 error) {
+	args := []interface{}{}
 	result, err := s.client.Request(s.module, s.object, "ZDBPrepare", args...)
 	if err != nil {
 		panic(err)
diff --git a/pkg/stubs/storage_stub.go b/pkg/stubs/storage_stub.go
index 7d59bdba..e16cc26f 100644
--- a/pkg/stubs/storage_stub.go
+++ b/pkg/stubs/storage_stub.go
@@ -38,30 +38,6 @@ func (s *StorageModuleStub) Allocate(arg0 string, arg1 pkg.DeviceType, arg2 uint
 	return
 }
 
-func (s *StorageModuleStub) BrokenDevices() (ret0 []pkg.BrokenDevice) {
-	args := []interface{}{}
-	result, err := s.client.Request(s.module, s.object, "BrokenDevices", args...)
-	if err != nil {
-		panic(err)
-	}
-	if err := result.Unmarshal(0, &ret0); err != nil {
-		panic(err)
-	}
-	return
-}
-
-func (s *StorageModuleStub) BrokenPools() (ret0 []pkg.BrokenPool) {
-	args := []interface{}{}
-	result, err := s.client.Request(s.module, s.object, "BrokenPools", args...)
-	if err != nil {
-		panic(err)
-	}
-	if err := result.Unmarshal(0, &ret0); err != nil {
-		panic(err)
-	}
-	return
-}
-
 func (s *StorageModuleStub) CreateFilesystem(arg0 string, arg1 uint64, arg2 pkg.DeviceType) (ret0 string, ret1 error) {
 	args := []interface{}{arg0, arg1, arg2}
 	result, err := s.client.Request(s.module, s.object, "CreateFilesystem", args...)
diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go
index 19554bd7..45c4e04c 100644
--- a/pkg/upgrade/upgrade.go
+++ b/pkg/upgrade/upgrade.go
@@ -21,14 +21,16 @@ import (
 
 type hookType string
 
+const (
+	provisionModuleName = "provisiond"
+)
+
 var (
 	// ErrRestartNeeded is returned if upgraded requires a restart
 	ErrRestartNeeded = fmt.Errorf("restart needed")
 
 	// services that can't be uninstalled with normal procedure
 	protected = []string{"identityd", "redis"}
-
-	flistIdentityPath = "/bin/identityd"
 )
 
 // Upgrader is the component that is responsible
@@ -280,11 +282,6 @@ func (u *Upgrader) uninstall(flist listFListInfo) error {
 			continue
 		}
 
-		if file.Path == flistIdentityPath {
-			log.Debug().Str("file", file.Path).Msg("skip deleting file")
-			continue
-		}
-
 		if err := os.Remove(file.Path); err != nil {
 			log.Error().Err(err).Str("file", file.Path).Msg("failed to remove file")
 		}
@@ -335,7 +332,7 @@ func (u *Upgrader) applyUpgrade(from, to FListEvent) error {
 
 	log.Debug().Strs("services", names).Msg("new services")
 
-	if err := copyRecursive(flistRoot, "/", flistIdentityPath); err != nil {
+	if err := copyRecursive(flistRoot, "/"); err != nil {
 		return err
 	}
 
@@ -364,15 +361,15 @@ func copyRecursive(source string, destination string, skip ...string) error {
 			return err
 		}
 
-		dest := filepath.Join(destination, rel)
-		if isIn(dest, skip) {
+		if isIn(rel, skip) {
 			if info.IsDir() {
 				return filepath.SkipDir
 			}
-			log.Debug().Str("file", dest).Msg("skipping file")
+
 			return nil
 		}
 
+		dest := filepath.Join(destination, rel)
 		if info.IsDir() {
 			if err := os.MkdirAll(dest, info.Mode()); err != nil {
 				return err
diff --git a/tools/bcdb_mock/node_handlers.go b/tools/bcdb_mock/node_handlers.go
index 38ff1cac..69dc57e0 100644
--- a/tools/bcdb_mock/node_handlers.go
+++ b/tools/bcdb_mock/node_handlers.go
@@ -191,12 +191,13 @@ func (s *nodeStore) configurePublic(w http.ResponseWriter, r *http.Request) {
 	}
 
 	cfg := directory.TfgridNodePublicIface1{
-		Gw4:    iface.GW4,
-		Gw6:    iface.GW6,
-		Ipv4:   iface.IPv4.ToSchema(),
-		Ipv6:   iface.IPv6.ToSchema(),
-		Master: iface.Master,
-		Type:   directory.TfgridNodePublicIface1TypeMacvlan,
+		Gw4:     iface.GW4,
+		Gw6:     iface.GW6,
+		Ipv4:    iface.IPv4.ToSchema(),
+		Ipv6:    iface.IPv6.ToSchema(),
+		Master:  iface.Master,
+		Type:    directory.TfgridNodePublicIface1TypeMacvlan,
+		Version: int64(iface.Version),
 	}
 
 	nodeID := mux.Vars(r)["node_id"]
diff --git a/tools/bcdb_mock/nodes_store.go b/tools/bcdb_mock/nodes_store.go
index 72fb10ad..616036cc 100644
--- a/tools/bcdb_mock/nodes_store.go
+++ b/tools/bcdb_mock/nodes_store.go
@@ -198,12 +198,6 @@ func (s *nodeStore) SetPublicConfig(nodeID string, cfg directory.TfgridNodePubli
 		return err
 	}
 
-	if node.PublicConfig == nil {
-		cfg.Version = 0
-	} else {
-		cfg.Version = node.PublicConfig.Version + 1
-	}
-
 	node.PublicConfig = &cfg
 	return nil
 }
diff --git a/tools/tffarmer/network.go b/tools/tffarmer/network.go
index b65a44b9..92a35846 100644
--- a/tools/tffarmer/network.go
+++ b/tools/tffarmer/network.go
@@ -63,19 +63,14 @@ func configPublic(c *cli.Context) error {
 	}
 
 	node := c.Args().First()
-	pubIface := &types.PubIface{
+
+	if err := db.SetPublicIface(pkg.StrIdentifier(node), &types.PubIface{
 		Master: iface,
+		IPv4:   types.NewIPNet(nv4),
+		IPv6:   types.NewIPNet(nv6),
 		GW4:    gw4,
 		GW6:    gw6,
-	}
-	if nv4 != nil {
-		pubIface.IPv4 = types.NewIPNet(nv4)
-	}
-	if nv6 != nil {
-		pubIface.IPv6 = types.NewIPNet(nv6)
-	}
-
-	if err := db.SetPublicIface(pkg.StrIdentifier(node), pubIface); err != nil {
+	}); err != nil {
 		return err
 	}
 	fmt.Printf("public interface configured on node %s\n", node)


Test patch:
diff --git a/.github/workflows/test-bootstrap.yaml b/.github/workflows/test-bootstrap.yaml
deleted file mode 100644
index b5a35f30..00000000
--- a/.github/workflows/test-bootstrap.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Bootstrap Tests
-on:
-  push:
-    paths:
-      - 'bootstrap/bootstrap/**'
-      - '.github/workflows/test-bootstrap.yaml'
-jobs:
-  bootstrap:
-    name: Running Bootstrap Tests
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@master
-      - name: Prepare musl
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y musl musl-tools
-      - name: Setup toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          target: x86_64-unknown-linux-musl
-          override: true
-      - uses: actions-rs/cargo@v1
-      - name: Test bootstrap
-        run: make test
-        working-directory: bootstrap/bootstrap
-      - name: Build bootstrap
-        run: make release
-        working-directory: bootstrap/bootstrap
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d30a7e49..0eff4c04 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -50,3 +50,26 @@ jobs:
         make
       env:
         GO111MODULE: on
+  bootstrap:
+    name: Running Bootstrap Tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@master
+      - name: Prepare musl
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y musl musl-tools
+      - name: Setup toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          target: x86_64-unknown-linux-musl
+          override: true
+      - uses: actions-rs/cargo@v1
+      - name: Test bootstrap
+        run: make test
+        working-directory: bootstrap/bootstrap
+      - name: Build bootstrap
+        run: make release
+        working-directory: bootstrap/bootstrap
diff --git a/pkg/gedis/commands_provision_test.go b/pkg/gedis/commands_provision_test.go
index 3175a00e..ec7277aa 100644
--- a/pkg/gedis/commands_provision_test.go
+++ b/pkg/gedis/commands_provision_test.go
@@ -30,7 +30,7 @@ func TestProvisionGet(t *testing.T) {
 
 	workload := `{"workload_id": 10, "type": 0, "size": 100}`
 
-	conn.On("Do", "tfgrid.workloads.workload_manager.workload_get", mustMarshal(t, args)).
+	conn.On("Do", "tfgrid.directory.workload_manager.workload_get", mustMarshal(t, args)).
 		Return(mustMarshal(t, Args{
 			"workload_id": id,
 			"type":        types.TfgridReservationWorkload1TypeVolume,
@@ -63,7 +63,7 @@ func TestProvisionPoll(t *testing.T) {
 	workloadVol := `{"workload_id": 1, "type": 0, "size": 100}`
 	workloadZdb := `{"workload_id": 1, "mode": 0, "size": 100}`
 
-	conn.On("Do", "tfgrid.workloads.workload_manager.workloads_list", mustMarshal(t, args)).
+	conn.On("Do", "tfgrid.directory.workload_manager.workloads_list", mustMarshal(t, args)).
 		Return(mustMarshal(t, Args{
 			"workloads": []types.TfgridReservationWorkload1{
 				{
@@ -93,7 +93,7 @@ func TestProvisionPoll(t *testing.T) {
 		"cursor":  10,
 	}
 
-	conn.On("Do", "tfgrid.workloads.workload_manager.workloads_list", mustMarshal(t, args)).
+	conn.On("Do", "tfgrid.directory.workload_manager.workloads_list", mustMarshal(t, args)).
 		Return(mustMarshal(t, Args{
 			"workloads": []types.TfgridReservationWorkload1{
 				{
@@ -148,7 +148,7 @@ func TestProvisionFeedback(t *testing.T) {
 		},
 	}
 
-	conn.On("Do", "tfgrid.workloads.workload_manager.set_workload_result", mustMarshal(t, args)).
+	conn.On("Do", "tfgrid.directory.workload_manager.set_workload_result", mustMarshal(t, args)).
 		Return(nil, nil)
 
 	err := gedis.Feedback(id, &result)
@@ -197,7 +197,7 @@ func TestProvisionReserve(t *testing.T) {
 		},
 	}
 
-	conn.On("Do", "tfgrid.workloads.workload_manager.reservation_register", mock.MatchedBy(func(in []byte) bool {
+	conn.On("Do", "tfgrid.directory.workload_manager.reservation_register", mock.MatchedBy(func(in []byte) bool {
 		EqualJSON(t, mustMarshal(t, args), in)
 		return true
 	})).
diff --git a/pkg/provision/zdb_test.go b/pkg/provision/zdb_test.go
index 4d204863..61fd2944 100644
--- a/pkg/provision/zdb_test.go
+++ b/pkg/provision/zdb_test.go
@@ -173,10 +173,8 @@ func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) {
 			ReadOnly: false,
 		}).Return("/path/to/volume", nil)
 
-	// The hw address in this mock call, is based on the VolumeID above. changing the VolumeID
-	// will require this HW address to change
 	client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"},
-		"ZDBPrepare", []byte{0x7e, 0xcc, 0xc3, 0x81, 0xa2, 0x2e}).Return("net-ns", nil)
+		"ZDBPrepare").Return("net-ns", nil)
 
 	client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"},
 		"Run",