[lxc-devel] [lxd/master] Rework container state

stgraber on Github lxc-bot at linuxcontainers.org
Tue Feb 23 21:57:55 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 319 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160223/5e46a30e/attachment.bin>
-------------- next part --------------
From dbc26f0159c12e308c426464ffe6f2e102987767 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 23 Feb 2016 14:02:27 -0500
Subject: [PATCH 1/2] Fix file push permissions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Reported-by: Martin Pitt
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client.go b/client.go
index 709f49e..d63c75d 100644
--- a/client.go
+++ b/client.go
@@ -1432,7 +1432,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode os.
 	}
 	req.Header.Set("User-Agent", shared.UserAgent)
 
-	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", mode))
+	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", mode.Perm()))
 	req.Header.Set("X-LXD-uid", strconv.FormatUint(uint64(uid), 10))
 	req.Header.Set("X-LXD-gid", strconv.FormatUint(uint64(gid), 10))
 

From 38d60684ad66ec68222aab814390fc4765876517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 23 Feb 2016 00:41:13 -0500
Subject: [PATCH 2/2] Rework container state
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reworks and significantly extends the current container state API
endpoint.

It speeds up fetching network information (and gets more of that) and
adds memory disk usage to the API.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/info.go          |  27 ++--
 lxc/list.go          |  24 +--
 lxd/container_lxc.go | 207 ++++++++++++++++++--------
 lxd/main.go          | 126 +++++++++++++++-
 lxd/mntnsexec.go     | 387 -------------------------------------------------
 lxd/nsexec.go        | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/storage.go       |  11 ++
 lxd/storage_btrfs.go |  39 +++++
 lxd/storage_dir.go   |   4 +
 lxd/storage_lvm.go   |   6 +-
 lxd/storage_zfs.go   |  20 +++
 po/lxd.pot           | 140 +++++++++---------
 shared/container.go  |  51 +++++--
 specs/rest-api.md    | 152 +++++++++++++++----
 test/suites/basic.sh |   4 +-
 15 files changed, 1015 insertions(+), 585 deletions(-)
 delete mode 100644 lxd/mntnsexec.go
 create mode 100644 lxd/nsexec.go

diff --git a/lxc/info.go b/lxc/info.go
index 1cd1840..9f76181 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -96,22 +96,25 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		fmt.Printf(i18n.G("Type: persistent") + "\n")
 	}
 	fmt.Printf(i18n.G("Profiles: %s")+"\n", strings.Join(ct.Profiles, ", "))
-	if cs.Init != 0 {
-		fmt.Printf(i18n.G("Init: %d")+"\n", cs.Init)
-		fmt.Printf(i18n.G("Processcount: %d")+"\n", cs.Processcount)
-		fmt.Printf(i18n.G("Ips:") + "\n")
-		foundone := false
-		for _, ip := range cs.Ips {
+	if cs.Pid != 0 {
+		fmt.Printf(i18n.G("Pid: %d")+"\n", cs.Pid)
+		fmt.Printf(i18n.G("Processes: %d")+"\n", cs.Processes)
+
+		ipInfo := ""
+		for netName, net := range cs.Network {
 			vethStr := ""
-			if ip.HostVeth != "" {
-				vethStr = fmt.Sprintf("\t%s", ip.HostVeth)
+			if net.HostName != "" {
+				vethStr = fmt.Sprintf("\t%s", net.HostName)
 			}
 
-			fmt.Printf("  %s:\t%s\t%s%s\n", ip.Interface, ip.Protocol, ip.Address, vethStr)
-			foundone = true
+			for _, addr := range net.Addresses {
+				ipInfo += fmt.Sprintf("  %s:\t%s\t%s%s\n", netName, addr.Family, addr.Address, vethStr)
+			}
 		}
-		if !foundone {
-			fmt.Println(i18n.G("(none)"))
+
+		if ipInfo != "" {
+			fmt.Printf(i18n.G("Ips:") + "\n")
+			fmt.Printf(ipInfo)
 		}
 	}
 
diff --git a/lxc/list.go b/lxc/list.go
index c413fbb..cbac604 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -275,13 +275,15 @@ func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.Co
 func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
 	if cInfo.StatusCode == shared.Running || cInfo.StatusCode == shared.Frozen {
 		ipv4s := []string{}
-		for _, ip := range cState.Ips {
-			if ip.Interface == "lo" {
+		for netName, net := range cState.Network {
+			if net.Type == "loopback" {
 				continue
 			}
 
-			if ip.Protocol == "IPV4" {
-				ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
+			for _, addr := range net.Addresses {
+				if addr.Family == "inet" {
+					ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", addr.Address, netName))
+				}
 			}
 		}
 		return strings.Join(ipv4s, "\n")
@@ -293,13 +295,15 @@ func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
 	if cInfo.StatusCode == shared.Running || cInfo.StatusCode == shared.Frozen {
 		ipv6s := []string{}
-		for _, ip := range cState.Ips {
-			if ip.Interface == "lo" {
+		for netName, net := range cState.Network {
+			if net.Type == "loopback" {
 				continue
 			}
 
-			if ip.Protocol == "IPV6" {
-				ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
+			for _, addr := range net.Addresses {
+				if addr.Family == "inet6" {
+					ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", addr.Address, netName))
+				}
 			}
 		}
 		return strings.Join(ipv6s, "\n")
@@ -321,8 +325,8 @@ func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *
 }
 
 func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
-	if cState.Init != 0 {
-		return fmt.Sprintf("%d", cState.Init)
+	if cState.Pid != 0 {
+		return fmt.Sprintf("%d", cState.Pid)
 	} else {
 		return ""
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index bcff879..9983067 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1400,10 +1400,22 @@ func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
 	}
 
 	if c.IsRunning() {
+		memory, err := c.memoryState()
+		if err != nil {
+			return nil, err
+		}
+
+		network, err := c.networkState()
+		if err != nil {
+			return nil, err
+		}
+
 		pid := c.InitPID()
-		status.Init = pid
-		status.Processcount = c.processcountGet()
-		status.Ips = c.ipsGet()
+		status.Disk = c.diskState()
+		status.Memory = *memory
+		status.Network = network
+		status.Pid = int64(pid)
+		status.Processes = c.processesState()
 	}
 
 	return &status, nil
@@ -2652,67 +2664,111 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 	return nil
 }
 
-func (c *containerLXC) ipsGet() []shared.Ip {
-	ips := []shared.Ip{}
+func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
+	disk := map[string]shared.ContainerStateDisk{}
 
-	// Load the go-lxc struct
-	err := c.initLXC()
-	if err != nil {
-		return nil
+	for name, d := range c.expandedDevices {
+		if d["type"] != "disk" {
+			continue
+		}
+
+		if d["path"] != "/" {
+			continue
+		}
+
+		usage, err := c.storage.ContainerGetUsage(c)
+		if err != nil {
+			continue
+		}
+
+		disk[name] = shared.ContainerStateDisk{Usage: usage}
 	}
 
-	// Return empty list if not running
-	if !c.IsRunning() {
-		return ips
+	return disk
+}
+
+func (c *containerLXC) memoryState() (*shared.ContainerStateMemory, error) {
+	memory := shared.ContainerStateMemory{}
+
+	if !cgMemoryController {
+		return &memory, nil
 	}
 
-	// Get the list of interfaces
-	names, err := c.c.Interfaces()
+	// Memory in bytes
+	value, err := c.CGroupGet("memory.usage_in_bytes")
+	valueInt, err := strconv.ParseInt(value, 10, 64)
 	if err != nil {
-		return nil
+		return nil, err
+	}
+	memory.Usage = valueInt
+
+	// Memory peak in bytes
+	value, err = c.CGroupGet("memory.max_usage_in_bytes")
+	valueInt, err = strconv.ParseInt(value, 10, 64)
+	if err != nil {
+		return nil, err
 	}
 
-	// Build the IPs list by iterating through all the interfaces
-	for _, n := range names {
-		// Get all the IPs
-		addresses, err := c.c.IPAddress(n)
+	memory.UsagePeak = valueInt
+
+	if cgSwapAccounting {
+		// Swap in bytes
+		value, err := c.CGroupGet("memory.memsw.usage_in_bytes")
+		valueInt, err := strconv.ParseInt(value, 10, 64)
 		if err != nil {
-			continue
+			return nil, err
 		}
 
-		// Look for the host side interface name
-		veth := ""
-		for i := 0; i < len(c.c.ConfigItem("lxc.network")); i++ {
-			nicName := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.name", i))[0]
-			if nicName != n {
-				continue
-			}
-
-			interfaceType := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.type", i))
-			if interfaceType[0] != "veth" {
-				continue
-			}
+		memory.SwapUsage = valueInt - memory.Usage
 
-			veth = c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))[0]
-			break
+		// Swap peak in bytes
+		value, err = c.CGroupGet("memory.memsw.max_usage_in_bytes")
+		valueInt, err = strconv.ParseInt(value, 10, 64)
+		if err != nil {
+			return nil, err
 		}
 
-		// Render the result
-		for _, a := range addresses {
-			ip := shared.Ip{Interface: n, Address: a, HostVeth: veth}
-			if net.ParseIP(a).To4() == nil {
-				ip.Protocol = "IPV6"
-			} else {
-				ip.Protocol = "IPV4"
-			}
-			ips = append(ips, ip)
-		}
+		memory.SwapUsagePeak = valueInt - memory.UsagePeak
 	}
 
-	return ips
+	return &memory, nil
 }
 
-func (c *containerLXC) processcountGet() int {
+func (c *containerLXC) networkState() (map[string]shared.ContainerStateNetwork, error) {
+	pid := c.InitPID()
+	if pid < 1 {
+		return nil, fmt.Errorf("Container isn't running")
+	}
+
+	// Get the network state from the container
+	out, err := exec.Command(
+		c.daemon.execPath,
+		"forkgetnet",
+		fmt.Sprintf("%d", pid)).CombinedOutput()
+
+	// Process forkgetnet response
+	if err != nil {
+		return nil, fmt.Errorf("Error calling 'lxd forkgetnet %d': %s", pid, string(out))
+	}
+
+	networks := map[string]shared.ContainerStateNetwork{}
+
+	err = json.Unmarshal(out, &networks)
+	if err != nil {
+		return nil, err
+	}
+
+	// Add HostName field
+	result := map[string]shared.ContainerStateNetwork{}
+	for netName, net := range networks {
+		net.HostName = c.getHostInterface(netName)
+		result[netName] = net
+	}
+
+	return result, nil
+}
+
+func (c *containerLXC) processesState() int64 {
 	// Return 0 if not running
 	pid := c.InitPID()
 	if pid == -1 {
@@ -2721,15 +2777,15 @@ func (c *containerLXC) processcountGet() int {
 
 	if cgPidsController {
 		value, err := c.CGroupGet("pids.current")
-		valueInt, err := strconv.Atoi(value)
+		valueInt, err := strconv.ParseInt(value, 10, 64)
 		if err != nil {
-			return 0
+			return -1
 		}
 
 		return valueInt
 	}
 
-	pids := []int{pid}
+	pids := []int64{int64(pid)}
 
 	// Go through the pid list, adding new pids at the end so we go through them all
 	for i := 0; i < len(pids); i++ {
@@ -2742,14 +2798,14 @@ func (c *containerLXC) processcountGet() int {
 
 		content := strings.Split(string(fcont), " ")
 		for j := 0; j < len(content); j++ {
-			pid, err := strconv.Atoi(content[j])
+			pid, err := strconv.ParseInt(content[j], 10, 64)
 			if err == nil {
 				pids = append(pids, pid)
 			}
 		}
 	}
 
-	return len(pids)
+	return int64(len(pids))
 }
 
 func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *tar.Writer, path string, fi os.FileInfo) error {
@@ -3774,6 +3830,41 @@ func (c *containerLXC) setNetworkPriority() error {
 	return nil
 }
 
+func (c *containerLXC) getHostInterface(name string) string {
+	if c.IsRunning() {
+		for i := 0; i < len(c.c.ConfigItem("lxc.network")); i++ {
+			nicName := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.name", i))[0]
+			if nicName != name {
+				continue
+			}
+
+			veth := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))[0]
+			if veth != "" {
+				return veth
+			}
+		}
+	}
+
+	for _, dev := range c.expandedDevices {
+		if dev["type"] != "nic" {
+			continue
+		}
+
+		m, err := c.fillNetworkDevice(name, dev)
+		if err != nil {
+			m = dev
+		}
+
+		if m["name"] != name {
+			continue
+		}
+
+		return m["host_name"]
+	}
+
+	return ""
+}
+
 func (c *containerLXC) setNetworkLimits(name string, m shared.Device) error {
 	// We can only do limits on some network type
 	if m["nictype"] != "bridged" && m["nictype"] != "p2p" {
@@ -3798,19 +3889,7 @@ func (c *containerLXC) setNetworkLimits(name string, m shared.Device) error {
 	}
 
 	// Look for the host side interface name
-	veth := m["host_name"]
-
-	if veth == "" {
-		for i := 0; i < len(c.c.ConfigItem("lxc.network")); i++ {
-			nicName := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.name", i))[0]
-			if nicName != m["name"] {
-				continue
-			}
-
-			veth = c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))[0]
-			break
-		}
-	}
+	veth := c.getHostInterface(m["name"])
 
 	if veth == "" {
 		return fmt.Errorf("LXC doesn't now about this device and the host_name property isn't set, can't find host side veth name")
diff --git a/lxd/main.go b/lxd/main.go
index 81c48f6..4c9518c 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -2,8 +2,11 @@ package main
 
 import (
 	"bufio"
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"math/rand"
+	"net"
 	"net/http"
 	"os"
 	"os/exec"
@@ -131,6 +134,8 @@ func run() error {
 		fmt.Printf("        How long to wait before failing\n")
 
 		fmt.Printf("\n\nInternal commands (don't call these directly):\n")
+		fmt.Printf("    forkgetnett\n")
+		fmt.Printf("        Get container network information\n")
 		fmt.Printf("    forkgetfile\n")
 		fmt.Printf("        Grab a file from a running container\n")
 		fmt.Printf("    forkmigrate\n")
@@ -184,12 +189,15 @@ func run() error {
 
 	// Process sub-commands
 	if len(os.Args) > 1 {
-		// "forkputfile" and "forkgetfile" are handled specially in mntnsexec.go
+		// "forkputfile", "forkgetfile", "forkmount" and "forkumount" are handled specially in nsexec.go
+		// "forkgetnet" is partially handled in nsexec.go (setns)
 		switch os.Args[1] {
 		case "activateifneeded":
 			return activateIfNeeded()
 		case "daemon":
 			return daemon()
+		case "forkgetnet":
+			return printnet()
 		case "forkmigrate":
 			return MigrateContainer(os.Args[1:])
 		case "forkstart":
@@ -776,3 +784,119 @@ func setupLXD() error {
 	fmt.Printf("LXD has been successfully configured.\n")
 	return nil
 }
+
+func printnet() error {
+	networks := map[string]shared.ContainerStateNetwork{}
+
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	stats := map[string][]int64{}
+
+	content, err := ioutil.ReadFile("/proc/net/dev")
+	if err == nil {
+		for _, line := range strings.Split(string(content), "\n") {
+			fields := strings.Fields(line)
+
+			if len(fields) != 17 {
+				continue
+			}
+
+			rxBytes, err := strconv.ParseInt(fields[1], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			rxPackets, err := strconv.ParseInt(fields[2], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txBytes, err := strconv.ParseInt(fields[9], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txPackets, err := strconv.ParseInt(fields[10], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			intName := strings.TrimSuffix(fields[0], ":")
+			stats[intName] = []int64{rxBytes, rxPackets, txBytes, txPackets}
+		}
+	}
+
+	for _, netIf := range interfaces {
+		netState := "down"
+		netType := "unknown"
+
+		if netIf.Flags&net.FlagBroadcast > 0 {
+			netType = "broadcast"
+		}
+
+		if netIf.Flags&net.FlagPointToPoint > 0 {
+			netType = "point-to-point"
+		}
+
+		if netIf.Flags&net.FlagLoopback > 0 {
+			netType = "loopback"
+		}
+
+		if netIf.Flags&net.FlagUp > 0 {
+			netState = "up"
+		}
+
+		network := shared.ContainerStateNetwork{
+			Addresses: []shared.ContainerStateNetworkAddress{},
+			Counters:  shared.ContainerStateNetworkCounters{},
+			Hwaddr:    netIf.HardwareAddr.String(),
+			Mtu:       netIf.MTU,
+			State:     netState,
+			Type:      netType,
+		}
+
+		addrs, err := netIf.Addrs()
+		if err == nil {
+			for _, addr := range addrs {
+				fields := strings.SplitN(addr.String(), "/", 2)
+				if len(fields) != 2 {
+					continue
+				}
+
+				family := "inet"
+				if strings.Contains(fields[0], ":") {
+					family = "inet6"
+				}
+
+				address := shared.ContainerStateNetworkAddress{}
+				address.Family = family
+				address.Address = fields[0]
+				address.Netmask = fields[1]
+
+				network.Addresses = append(network.Addresses, address)
+			}
+		}
+
+		counters, ok := stats[netIf.Name]
+		if ok {
+			network.Counters.BytesReceived = counters[0]
+			network.Counters.PacketsReceived = counters[1]
+			network.Counters.BytesSent = counters[2]
+			network.Counters.PacketsSent = counters[3]
+		}
+
+		networks[netIf.Name] = network
+	}
+
+	buf, err := json.Marshal(networks)
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("%s\n", buf)
+
+	return nil
+}
diff --git a/lxd/mntnsexec.go b/lxd/mntnsexec.go
deleted file mode 100644
index 41c0aa9..0000000
--- a/lxd/mntnsexec.go
+++ /dev/null
@@ -1,387 +0,0 @@
-/**
- * This file is a bit funny. The goal here is to use setns() to manipulate
- * files inside the container, so we don't have to reason about the paths to
- * make sure they don't escape (we can simply rely on the kernel for
- * correctness). Unfortunately, you can't setns() to a mount namespace with a
- * multi-threaded program, which every golang binary is. However, by declaring
- * our init as an initializer, we can capture process control before it is
- * transferred to the golang runtime, so we can then setns() as we'd like
- * before golang has a chance to set up any threads. So, we implement two new
- * lxd fork* commands which are captured here, and take a file on the host fs
- * and copy it into the container ns.
- *
- * An alternative to this would be to move this code into a separate binary,
- * which of course has problems of its own when it comes to packaging (how do
- * we find the binary, what do we do if someone does file push and it is
- * missing, etc.). After some discussion, even though the embedded method is
- * somewhat convoluted, it was preferred.
- */
-package main
-
-/*
-#define _GNU_SOURCE
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/mount.h>
-#include <sched.h>
-#include <linux/sched.h>
-#include <linux/limits.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <errno.h>
-#include <alloca.h>
-#include <libgen.h>
-
-// This expects:
-//  ./lxd forkputfile /source/path <pid> /target/path
-// or
-//  ./lxd forkgetfile /target/path <pid> /soruce/path <uid> <gid> <mode>
-// i.e. 8 arguments, each which have a max length of PATH_MAX.
-// Unfortunately, lseek() and fstat() both fail (EINVAL and 0 size) for
-// procfs. Also, we can't mmap, because procfs doesn't support that, either.
-//
-#define CMDLINE_SIZE (8 * PATH_MAX)
-
-int mkdir_p(const char *dir, mode_t mode)
-{
-	const char *tmp = dir;
-	const char *orig = dir;
-	char *makeme;
-
-	do {
-		dir = tmp + strspn(tmp, "/");
-		tmp = dir + strcspn(dir, "/");
-		makeme = strndup(orig, dir - orig);
-		if (*makeme) {
-			if (mkdir(makeme, mode) && errno != EEXIST) {
-				fprintf(stderr, "failed to create directory '%s'", makeme);
-				free(makeme);
-				return -1;
-			}
-		}
-		free(makeme);
-	} while(tmp != dir);
-
-	return 0;
-}
-
-int copy(int target, int source)
-{
-	ssize_t n;
-	char buf[1024];
-
-	if (ftruncate(target, 0) < 0) {
-		perror("truncate");
-		return -1;
-	}
-
-	while ((n = read(source, buf, 1024)) > 0) {
-		if (write(target, buf, n) != n) {
-			perror("write");
-			return -1;
-		}
-	}
-
-	if (n < 0) {
-		perror("read");
-		return -1;
-	}
-
-	return 0;
-}
-
-int dosetns(int pid, char *nstype) {
-	int mntns;
-	char buf[PATH_MAX];
-
-	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
-	mntns = open(buf, O_RDONLY);
-	if (mntns < 0) {
-		perror("open mntns");
-		return -1;
-	}
-
-	if (setns(mntns, 0) < 0) {
-		perror("setns");
-		close(mntns);
-		return -1;
-	}
-	close(mntns);
-
-	return 0;
-}
-
-int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode) {
-	int host_fd, container_fd;
-	int ret = -1;
-	int container_open_flags;
-
-	host_fd = open(host, O_RDWR);
-	if (host_fd < 0) {
-		perror("open host");
-		return -1;
-	}
-
-	container_open_flags = O_RDWR;
-	if (is_put)
-		container_open_flags |= O_CREAT;
-
-	if (pid > 0) {
-		if (dosetns(pid, "mnt") < 0)
-			goto close_host;
-	} else {
-		if (chroot(rootfs) < 0)
-			goto close_host;
-
-		if (chdir("/") < 0)
-			goto close_host;
-	}
-
-	container_fd = open(container, container_open_flags, mode);
-	if (container_fd < 0) {
-		fprintf(stderr, "%s\n", strerror(errno));
-		goto close_host;
-	}
-
-	if (is_put) {
-		if (copy(container_fd, host_fd) < 0)
-			goto close_container;
-
-		if (fchown(container_fd, uid, gid) < 0) {
-			perror("fchown");
-			goto close_container;
-		}
-
-		ret = 0;
-	} else
-		ret = copy(host_fd, container_fd);
-
-close_container:
-	close(container_fd);
-close_host:
-	close(host_fd);
-	return ret;
-}
-
-#define ADVANCE_ARG_REQUIRED()					\
-	do {							\
-		while (*cur != 0)				\
-			cur++;					\
-		cur++;						\
-		if (size <= cur - buf) {			\
-			fprintf(stderr, "not enough arguments\n");	\
-			_exit(1);				\
-		}						\
-	} while(0)
-
-void ensure_dir(char *dest) {
-	struct stat sb;
-	if (stat(dest, &sb) == 0) {
-		if ((sb.st_mode & S_IFMT) == S_IFDIR)
-			return;
-		if (unlink(dest) < 0) {
-			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
-			_exit(1);
-		}
-	}
-	if (mkdir(dest, 0755) < 0) {
-		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
-		_exit(1);
-	}
-}
-
-void ensure_file(char *dest) {
-	struct stat sb;
-	int fd;
-
-	if (stat(dest, &sb) == 0) {
-		if ((sb.st_mode & S_IFMT) != S_IFDIR)
-			return;
-		if (rmdir(dest) < 0) {
-			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
-			_exit(1);
-		}
-	}
-
-	fd = creat(dest, 0755);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
-		_exit(1);
-	}
-	close(fd);
-}
-
-void create(char *src, char *dest) {
-	char *destdirname;
-	struct stat sb;
-	if (stat(src, &sb) < 0) {
-		fprintf(stderr, "source %s does not exist\n", src);
-		_exit(1);
-	}
-
-	destdirname = strdup(dest);
-	destdirname = dirname(destdirname);
-
-	if (mkdir_p(destdirname, 0755) < 0) {
-		fprintf(stderr, "failed to create path: %s\n", destdirname);
-		free(destdirname);
-		_exit(1);
-	}
-
-	switch (sb.st_mode & S_IFMT) {
-	case S_IFDIR:
-		ensure_dir(dest);
-		return;
-	default:
-		ensure_file(dest);
-		return;
-	}
-
-	free(destdirname);
-}
-
-void forkmount(char *buf, char *cur, ssize_t size) {
-	char *src, *dest, *opts;
-
-	ADVANCE_ARG_REQUIRED();
-	int pid = atoi(cur);
-
-	if (dosetns(pid, "mnt") < 0) {
-		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	ADVANCE_ARG_REQUIRED();
-	src = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	dest = cur;
-
-	create(src, dest);
-
-	if (access(src, F_OK) < 0) {
-		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	if (access(dest, F_OK) < 0) {
-		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	if (mount(src, dest, "none", MS_MOVE, NULL) < 0) {
-		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
-		_exit(1);
-	}
-
-	_exit(0);
-}
-
-void forkumount(char *buf, char *cur, ssize_t size) {
-	ADVANCE_ARG_REQUIRED();
-	int pid = atoi(cur);
-
-	if (dosetns(pid, "mnt") < 0) {
-		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	ADVANCE_ARG_REQUIRED();
-	if (access(cur, F_OK) < 0) {
-		fprintf(stderr, "Mount path doesn't exist: %s\n", strerror(errno));
-		_exit(1);
-	}
-
-	if (umount2(cur, MNT_DETACH) < 0) {
-		fprintf(stderr, "Error unmounting %s: %s\n", cur, strerror(errno));
-		_exit(1);
-	}
-	_exit(0);
-}
-
-void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
-	uid_t uid = 0;
-	gid_t gid = 0;
-	mode_t mode = 0;
-	char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
-	pid_t pid;
-
-	ADVANCE_ARG_REQUIRED();
-	rootfs = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	pid = atoi(cur);
-
-	ADVANCE_ARG_REQUIRED();
-	source = cur;
-
-	ADVANCE_ARG_REQUIRED();
-	target = cur;
-
-	if (is_put) {
-		ADVANCE_ARG_REQUIRED();
-		uid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		gid = atoi(cur);
-
-		ADVANCE_ARG_REQUIRED();
-		mode = atoi(cur);
-	}
-
-	printf("command: %s\n", command);
-	printf("source: %s\n", source);
-	printf("pid: %d\n", pid);
-	printf("target: %s\n", target);
-	printf("uid: %d\n", uid);
-	printf("gid: %d\n", gid);
-	printf("mode: %d\n", mode);
-
-	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode));
-}
-
-__attribute__((constructor)) void init(void) {
-	int cmdline;
-	char buf[CMDLINE_SIZE];
-	ssize_t size;
-	char *cur;
-
-	cmdline = open("/proc/self/cmdline", O_RDONLY);
-	if (cmdline < 0) {
-		perror("open");
-		_exit(232);
-	}
-
-	memset(buf, 0, sizeof(buf));
-	if ((size = read(cmdline, buf, sizeof(buf)-1)) < 0) {
-		close(cmdline);
-		perror("read");
-		_exit(232);
-	}
-	close(cmdline);
-
-	cur = buf;
-	// skip argv[0]
-	while (*cur != 0)
-		cur++;
-	cur++;
-	if (size <= cur - buf)
-		return;
-
-	if (strcmp(cur, "forkputfile") == 0) {
-		forkdofile(buf, cur, true, size);
-	} else if (strcmp(cur, "forkgetfile") == 0) {
-		forkdofile(buf, cur, false, size);
-	} else if (strcmp(cur, "forkmount") == 0) {
-		forkmount(buf, cur, size);
-	} else if (strcmp(cur, "forkumount") == 0) {
-		forkumount(buf, cur, size);
-	}
-}
-*/
-import "C"
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
new file mode 100644
index 0000000..481e27f
--- /dev/null
+++ b/lxd/nsexec.go
@@ -0,0 +1,402 @@
+/**
+ * This file is a bit funny. The goal here is to use setns() to manipulate
+ * files inside the container, so we don't have to reason about the paths to
+ * make sure they don't escape (we can simply rely on the kernel for
+ * correctness). Unfortunately, you can't setns() to a mount namespace with a
+ * multi-threaded program, which every golang binary is. However, by declaring
+ * our init as an initializer, we can capture process control before it is
+ * transferred to the golang runtime, so we can then setns() as we'd like
+ * before golang has a chance to set up any threads. So, we implement two new
+ * lxd fork* commands which are captured here, and take a file on the host fs
+ * and copy it into the container ns.
+ *
+ * An alternative to this would be to move this code into a separate binary,
+ * which of course has problems of its own when it comes to packaging (how do
+ * we find the binary, what do we do if someone does file push and it is
+ * missing, etc.). After some discussion, even though the embedded method is
+ * somewhat convoluted, it was preferred.
+ */
+package main
+
+/*
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sched.h>
+#include <linux/sched.h>
+#include <linux/limits.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <alloca.h>
+#include <libgen.h>
+#include <ifaddrs.h>
+
+// This expects:
+//  ./lxd forkputfile /source/path <pid> /target/path
+// or
+//  ./lxd forkgetfile /target/path <pid> /soruce/path <uid> <gid> <mode>
+// i.e. 8 arguments, each which have a max length of PATH_MAX.
+// Unfortunately, lseek() and fstat() both fail (EINVAL and 0 size) for
+// procfs. Also, we can't mmap, because procfs doesn't support that, either.
+//
+#define CMDLINE_SIZE (8 * PATH_MAX)
+
+int mkdir_p(const char *dir, mode_t mode)
+{
+	const char *tmp = dir;
+	const char *orig = dir;
+	char *makeme;
+
+	do {
+		dir = tmp + strspn(tmp, "/");
+		tmp = dir + strcspn(dir, "/");
+		makeme = strndup(orig, dir - orig);
+		if (*makeme) {
+			if (mkdir(makeme, mode) && errno != EEXIST) {
+				fprintf(stderr, "failed to create directory '%s'", makeme);
+				free(makeme);
+				return -1;
+			}
+		}
+		free(makeme);
+	} while(tmp != dir);
+
+	return 0;
+}
+
+int copy(int target, int source)
+{
+	ssize_t n;
+	char buf[1024];
+
+	if (ftruncate(target, 0) < 0) {
+		perror("truncate");
+		return -1;
+	}
+
+	while ((n = read(source, buf, 1024)) > 0) {
+		if (write(target, buf, n) != n) {
+			perror("write");
+			return -1;
+		}
+	}
+
+	if (n < 0) {
+		perror("read");
+		return -1;
+	}
+
+	return 0;
+}
+
+int dosetns(int pid, char *nstype) {
+	int mntns;
+	char buf[PATH_MAX];
+
+	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
+	mntns = open(buf, O_RDONLY);
+	if (mntns < 0) {
+		perror("open mntns");
+		return -1;
+	}
+
+	if (setns(mntns, 0) < 0) {
+		perror("setns");
+		close(mntns);
+		return -1;
+	}
+	close(mntns);
+
+	return 0;
+}
+
+int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode) {
+	int host_fd, container_fd;
+	int ret = -1;
+	int container_open_flags;
+
+	host_fd = open(host, O_RDWR);
+	if (host_fd < 0) {
+		perror("open host");
+		return -1;
+	}
+
+	container_open_flags = O_RDWR;
+	if (is_put)
+		container_open_flags |= O_CREAT;
+
+	if (pid > 0) {
+		if (dosetns(pid, "mnt") < 0)
+			goto close_host;
+	} else {
+		if (chroot(rootfs) < 0)
+			goto close_host;
+
+		if (chdir("/") < 0)
+			goto close_host;
+	}
+
+	container_fd = open(container, container_open_flags, mode);
+	if (container_fd < 0) {
+		fprintf(stderr, "%s\n", strerror(errno));
+		goto close_host;
+	}
+
+	if (is_put) {
+		if (copy(container_fd, host_fd) < 0)
+			goto close_container;
+
+		if (fchown(container_fd, uid, gid) < 0) {
+			perror("fchown");
+			goto close_container;
+		}
+
+		ret = 0;
+	} else
+		ret = copy(host_fd, container_fd);
+
+close_container:
+	close(container_fd);
+close_host:
+	close(host_fd);
+	return ret;
+}
+
+#define ADVANCE_ARG_REQUIRED()					\
+	do {							\
+		while (*cur != 0)				\
+			cur++;					\
+		cur++;						\
+		if (size <= cur - buf) {			\
+			fprintf(stderr, "not enough arguments\n");	\
+			_exit(1);				\
+		}						\
+	} while(0)
+
+void ensure_dir(char *dest) {
+	struct stat sb;
+	if (stat(dest, &sb) == 0) {
+		if ((sb.st_mode & S_IFMT) == S_IFDIR)
+			return;
+		if (unlink(dest) < 0) {
+			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
+			_exit(1);
+		}
+	}
+	if (mkdir(dest, 0755) < 0) {
+		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
+		_exit(1);
+	}
+}
+
+void ensure_file(char *dest) {
+	struct stat sb;
+	int fd;
+
+	if (stat(dest, &sb) == 0) {
+		if ((sb.st_mode & S_IFMT) != S_IFDIR)
+			return;
+		if (rmdir(dest) < 0) {
+			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	fd = creat(dest, 0755);
+	if (fd < 0) {
+		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
+		_exit(1);
+	}
+	close(fd);
+}
+
+void create(char *src, char *dest) {
+	char *destdirname;
+	struct stat sb;
+	if (stat(src, &sb) < 0) {
+		fprintf(stderr, "source %s does not exist\n", src);
+		_exit(1);
+	}
+
+	destdirname = strdup(dest);
+	destdirname = dirname(destdirname);
+
+	if (mkdir_p(destdirname, 0755) < 0) {
+		fprintf(stderr, "failed to create path: %s\n", destdirname);
+		free(destdirname);
+		_exit(1);
+	}
+
+	switch (sb.st_mode & S_IFMT) {
+	case S_IFDIR:
+		ensure_dir(dest);
+		return;
+	default:
+		ensure_file(dest);
+		return;
+	}
+
+	free(destdirname);
+}
+
+void forkmount(char *buf, char *cur, ssize_t size) {
+	char *src, *dest, *opts;
+
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	if (dosetns(pid, "mnt") < 0) {
+		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	ADVANCE_ARG_REQUIRED();
+	src = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	dest = cur;
+
+	create(src, dest);
+
+	if (access(src, F_OK) < 0) {
+		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	if (access(dest, F_OK) < 0) {
+		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	if (mount(src, dest, "none", MS_MOVE, NULL) < 0) {
+		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
+		_exit(1);
+	}
+
+	_exit(0);
+}
+
+void forkumount(char *buf, char *cur, ssize_t size) {
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	if (dosetns(pid, "mnt") < 0) {
+		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	ADVANCE_ARG_REQUIRED();
+	if (access(cur, F_OK) < 0) {
+		fprintf(stderr, "Mount path doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	if (umount2(cur, MNT_DETACH) < 0) {
+		fprintf(stderr, "Error unmounting %s: %s\n", cur, strerror(errno));
+		_exit(1);
+	}
+	_exit(0);
+}
+
+void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
+	uid_t uid = 0;
+	gid_t gid = 0;
+	mode_t mode = 0;
+	char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
+	pid_t pid;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	source = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	target = cur;
+
+	if (is_put) {
+		ADVANCE_ARG_REQUIRED();
+		uid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		gid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		mode = atoi(cur);
+	}
+
+	printf("command: %s\n", command);
+	printf("source: %s\n", source);
+	printf("pid: %d\n", pid);
+	printf("target: %s\n", target);
+	printf("uid: %d\n", uid);
+	printf("gid: %d\n", gid);
+	printf("mode: %d\n", mode);
+
+	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode));
+}
+
+void forkgetnet(char *buf, char *cur, ssize_t size) {
+	ADVANCE_ARG_REQUIRED();
+	int pid = atoi(cur);
+
+	if (dosetns(pid, "net") < 0) {
+		fprintf(stderr, "Failed setns to container network namespace: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	// The rest happens in Go
+}
+
+__attribute__((constructor)) void init(void) {
+	int cmdline;
+	char buf[CMDLINE_SIZE];
+	ssize_t size;
+	char *cur;
+
+	cmdline = open("/proc/self/cmdline", O_RDONLY);
+	if (cmdline < 0) {
+		perror("open");
+		_exit(232);
+	}
+
+	memset(buf, 0, sizeof(buf));
+	if ((size = read(cmdline, buf, sizeof(buf)-1)) < 0) {
+		close(cmdline);
+		perror("read");
+		_exit(232);
+	}
+	close(cmdline);
+
+	cur = buf;
+	// skip argv[0]
+	while (*cur != 0)
+		cur++;
+	cur++;
+	if (size <= cur - buf)
+		return;
+
+	if (strcmp(cur, "forkputfile") == 0) {
+		forkdofile(buf, cur, true, size);
+	} else if (strcmp(cur, "forkgetfile") == 0) {
+		forkdofile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkmount") == 0) {
+		forkmount(buf, cur, size);
+	} else if (strcmp(cur, "forkumount") == 0) {
+		forkumount(buf, cur, size);
+	} else if (strcmp(cur, "forkgetnet") == 0) {
+		forkgetnet(buf, cur, size);
+	}
+}
+*/
+import "C"
diff --git a/lxd/storage.go b/lxd/storage.go
index 95414c5..2af685a 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -136,6 +136,7 @@ type storage interface {
 	ContainerRename(container container, newName string) error
 	ContainerRestore(container container, sourceContainer container) error
 	ContainerSetQuota(container container, size int64) error
+	ContainerGetUsage(container container) (int64, error)
 
 	ContainerSnapshotCreate(
 		snapshotContainer container, sourceContainer container) error
@@ -446,6 +447,16 @@ func (lw *storageLogWrapper) ContainerSetQuota(
 	return lw.w.ContainerSetQuota(container, size)
 }
 
+func (lw *storageLogWrapper) ContainerGetUsage(
+	container container) (int64, error) {
+
+	lw.log.Debug(
+		"ContainerGetUsage",
+		log.Ctx{
+			"container": container.Name()})
+	return lw.w.ContainerGetUsage(container)
+}
+
 func (lw *storageLogWrapper) ContainerSnapshotCreate(
 	snapshotContainer container, sourceContainer container) error {
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 9cd0f09..e4ca28f 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -7,6 +7,7 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"syscall"
 
@@ -277,6 +278,10 @@ func (s *storageBtrfs) ContainerSetQuota(container container, size int64) error
 	return nil
 }
 
+func (s *storageBtrfs) ContainerGetUsage(container container) (int64, error) {
+	return s.subvolQGroupUsage(container.Path())
+}
+
 func (s *storageBtrfs) ContainerSnapshotCreate(
 	snapshotContainer container, sourceContainer container) error {
 
@@ -483,6 +488,40 @@ func (s *storageBtrfs) subvolQGroup(subvol string) (string, error) {
 	return qgroup, nil
 }
 
+func (s *storageBtrfs) subvolQGroupUsage(subvol string) (int64, error) {
+	output, err := exec.Command(
+		"btrfs",
+		"qgroup",
+		"show",
+		subvol,
+		"-e",
+		"-f").CombinedOutput()
+
+	if err != nil {
+		return -1, fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
+	}
+
+	for _, line := range strings.Split(string(output), "\n") {
+		if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
+			continue
+		}
+
+		fields := strings.Fields(line)
+		if len(fields) != 4 {
+			continue
+		}
+
+		usage, err := strconv.ParseInt(fields[2], 10, 64)
+		if err != nil {
+			continue
+		}
+
+		return usage, nil
+	}
+
+	return -1, fmt.Errorf("Unable to find current qgroup usage")
+}
+
 func (s *storageBtrfs) subvolDelete(subvol string) error {
 	// Attempt (but don't fail on) to delete any qgroup on the subvolume
 	qgroup, err := s.subvolQGroup(subvol)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index e6f7dfb..98d5320 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -174,6 +174,10 @@ func (s *storageDir) ContainerSetQuota(container container, size int64) error {
 	return fmt.Errorf("The directory container backend doesn't support quotas.")
 }
 
+func (s *storageDir) ContainerGetUsage(container container) (int64, error) {
+	return -1, fmt.Errorf("The directory container backend doesn't support quotas.")
+}
+
 func (s *storageDir) ContainerSnapshotCreate(
 	snapshotContainer container, sourceContainer container) error {
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index a7f7092..f0fd62e 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -641,7 +641,11 @@ func (s *storageLvm) ContainerRestore(
 }
 
 func (s *storageLvm) ContainerSetQuota(container container, size int64) error {
-	return fmt.Errorf("The directory container backend doesn't support quotas.")
+	return fmt.Errorf("The LVM container backend doesn't support quotas.")
+}
+
+func (s *storageLvm) ContainerGetUsage(container container) (int64, error) {
+	return -1, fmt.Errorf("The LVM container backend doesn't support quotas.")
 }
 
 func (s *storageLvm) ContainerSnapshotCreate(
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index a6ca4a6..ec6cdb1 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"strconv"
 	"strings"
 	"syscall"
 	"time"
@@ -433,6 +434,24 @@ func (s *storageZfs) ContainerSetQuota(container container, size int64) error {
 	return nil
 }
 
+func (s *storageZfs) ContainerGetUsage(container container) (int64, error) {
+	var err error
+
+	fs := fmt.Sprintf("containers/%s", container.Name())
+
+	value, err := s.zfsGet(fs, "used")
+	if err != nil {
+		return -1, err
+	}
+
+	valueInt, err := strconv.ParseInt(value, 10, 64)
+	if err != nil {
+		return -1, err
+	}
+
+	return valueInt, nil
+}
+
 func (s *storageZfs) ContainerSnapshotCreate(snapshotContainer container, sourceContainer container) error {
 	fields := strings.SplitN(snapshotContainer.Name(), shared.SnapshotDelimiter, 2)
 	cName := fields[0]
@@ -823,6 +842,7 @@ func (s *storageZfs) zfsGet(path string, key string) (string, error) {
 		"zfs",
 		"get",
 		"-H",
+		"-p",
 		"-o", "value",
 		key,
 		fmt.Sprintf("%s/%s", s.zfsPool, path)).CombinedOutput()
diff --git a/po/lxd.pot b/po/lxd.pot
index 1d5dc2c..15b8431 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-02-19 14:15-0500\n"
+        "POT-Creation-Date: 2016-02-23 00:41-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:535
+#: lxc/image.go:534
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -74,28 +74,28 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/info.go:113 lxc/profile.go:223
+#: lxc/profile.go:223
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:555 lxc/image.go:577
+#: lxc/image.go:553 lxc/image.go:575
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:559
+#: lxc/image.go:557
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/remote.go:49
+#: lxc/remote.go:50
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:205
+#: lxc/remote.go:206
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:316
+#: lxc/image.go:315
 msgid   "Aliases:"
 msgstr  ""
 
@@ -103,7 +103,7 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:299
+#: lxc/image.go:298 lxc/info.go:87
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
@@ -130,7 +130,7 @@ msgstr  ""
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:155
+#: lxc/remote.go:156
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
@@ -142,7 +142,7 @@ msgid   "Changes state of one or more containers to %s.\n"
         "lxc %s <name> [<name>...]"
 msgstr  ""
 
-#: lxc/remote.go:228
+#: lxc/remote.go:229
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -154,7 +154,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:491 lxc/config.go:556 lxc/image.go:633 lxc/profile.go:187
+#: lxc/config.go:491 lxc/config.go:556 lxc/image.go:631 lxc/profile.go:187
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -192,7 +192,7 @@ msgstr  ""
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:170
+#: lxc/remote.go:171
 msgid   "Could not create server cert dir"
 msgstr  ""
 
@@ -212,7 +212,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:304 lxc/info.go:88
+#: lxc/image.go:303 lxc/info.go:89
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -226,7 +226,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:558 lxc/image.go:579
+#: lxc/image.go:556 lxc/image.go:577
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -282,16 +282,16 @@ msgid   "Execute the specified command in a container.\n"
         "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... <command>"
 msgstr  ""
 
-#: lxc/image.go:308
+#: lxc/image.go:307
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:310
+#: lxc/image.go:309
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:264 lxc/image.go:556 lxc/image.go:578
+#: lxc/config.go:264 lxc/image.go:554 lxc/image.go:576
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -350,16 +350,11 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:374
+#: lxc/image.go:373
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/info.go:99
-#, c-format
-msgid   "Init: %d"
-msgstr  ""
-
 #: lxc/init.go:73
 msgid   "Initialize a container from a particular image.\n"
         "\n"
@@ -378,17 +373,17 @@ msgstr  ""
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:187
+#: lxc/file.go:186
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
 
-#: lxc/file.go:58
+#: lxc/file.go:57
 #, c-format
 msgid   "Invalid target %s"
 msgstr  ""
 
-#: lxc/info.go:101
+#: lxc/info.go:116
 msgid   "Ips:"
 msgstr  ""
 
@@ -441,7 +436,7 @@ msgid   "Lists the available resources.\n"
         "* p - pid of container init process"
 msgstr  ""
 
-#: lxc/info.go:155
+#: lxc/info.go:159
 msgid   "Log:"
 msgstr  ""
 
@@ -522,7 +517,7 @@ msgid   "Manage configuration.\n"
         "    lxc config set core.trust_password blah"
 msgstr  ""
 
-#: lxc/file.go:33
+#: lxc/file.go:32
 msgid   "Manage files on a container.\n"
         "\n"
         "lxc file pull <source> [<source>...] <target>\n"
@@ -532,7 +527,7 @@ msgid   "Manage files on a container.\n"
         "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>"
 msgstr  ""
 
-#: lxc/remote.go:36
+#: lxc/remote.go:37
 msgid   "Manage remote LXD servers.\n"
         "\n"
         "lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD] [--public]    Add the remote <name> at <url>.\n"
@@ -618,7 +613,7 @@ msgid   "Monitor activity on the LXD server.\n"
         "lxc monitor --type=logging"
 msgstr  ""
 
-#: lxc/file.go:175
+#: lxc/file.go:174
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
@@ -632,11 +627,11 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a local container.\n"
 msgstr  ""
 
-#: lxc/list.go:246 lxc/remote.go:295
+#: lxc/list.go:246 lxc/remote.go:296
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/list.go:315 lxc/remote.go:281
+#: lxc/list.go:319 lxc/remote.go:282
 msgid   "NO"
 msgstr  ""
 
@@ -657,7 +652,7 @@ msgstr  ""
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/image.go:366
+#: lxc/image.go:365
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -665,7 +660,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:460
+#: lxc/image.go:459
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -678,7 +673,7 @@ msgstr  ""
 msgid   "PID"
 msgstr  ""
 
-#: lxc/image.go:557 lxc/remote.go:297
+#: lxc/image.go:555 lxc/remote.go:298
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -694,6 +689,11 @@ msgstr  ""
 msgid   "Permisson denied, are you in the lxd group?"
 msgstr  ""
 
+#: lxc/info.go:100
+#, c-format
+msgid   "Pid: %d"
+msgstr  ""
+
 #: lxc/help.go:25
 msgid   "Presents details on how to use LXD.\n"
         "\n"
@@ -704,7 +704,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:492 lxc/config.go:557 lxc/image.go:634
+#: lxc/config.go:492 lxc/config.go:557 lxc/image.go:632
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -726,9 +726,9 @@ msgid   "Prints the version number of LXD.\n"
         "lxc version"
 msgstr  ""
 
-#: lxc/info.go:100
+#: lxc/info.go:101
 #, c-format
-msgid   "Processcount: %d"
+msgid   "Processes: %d"
 msgstr  ""
 
 #: lxc/profile.go:225
@@ -750,20 +750,20 @@ msgstr  ""
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
-#: lxc/info.go:97
+#: lxc/info.go:98
 #, c-format
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:312
+#: lxc/image.go:311
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:51
+#: lxc/remote.go:52
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:300
+#: lxc/image.go:299
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -774,7 +774,7 @@ msgid   "Publish containers as images.\n"
         "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-value]..."
 msgstr  ""
 
-#: lxc/remote.go:50
+#: lxc/remote.go:51
 msgid   "Remote admin password"
 msgstr  ""
 
@@ -792,7 +792,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:560
+#: lxc/image.go:558
 msgid   "SIZE"
 msgstr  ""
 
@@ -804,11 +804,11 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:163
+#: lxc/remote.go:164
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:225
+#: lxc/remote.go:226
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
@@ -825,15 +825,15 @@ msgid   "Set the current state of a resource back to a snapshot.\n"
         "lxc restore u1 snap0 # restore the snapshot"
 msgstr  ""
 
-#: lxc/file.go:45
+#: lxc/file.go:44
 msgid   "Set the file's gid on push"
 msgstr  ""
 
-#: lxc/file.go:46
+#: lxc/file.go:45
 msgid   "Set the file's perms on push"
 msgstr  ""
 
-#: lxc/file.go:44
+#: lxc/file.go:43
 msgid   "Set the file's uid on push"
 msgstr  ""
 
@@ -850,7 +850,7 @@ msgstr  ""
 msgid   "Size: %.2fMB"
 msgstr  ""
 
-#: lxc/info.go:126
+#: lxc/info.go:130
 msgid   "Snapshots:"
 msgstr  ""
 
@@ -859,7 +859,7 @@ msgstr  ""
 msgid   "Starting %s"
 msgstr  ""
 
-#: lxc/info.go:91
+#: lxc/info.go:92
 #, c-format
 msgid   "Status: %s"
 msgstr  ""
@@ -888,7 +888,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:301
+#: lxc/image.go:300
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -897,23 +897,23 @@ msgstr  ""
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
 
-#: lxc/info.go:93
+#: lxc/info.go:94
 msgid   "Type: ephemeral"
 msgstr  ""
 
-#: lxc/info.go:95
+#: lxc/info.go:96
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:561
+#: lxc/image.go:559
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:296
+#: lxc/remote.go:297
 msgid   "URL"
 msgstr  ""
 
-#: lxc/image.go:306
+#: lxc/image.go:305
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -943,7 +943,7 @@ msgstr  ""
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/list.go:313 lxc/remote.go:283
+#: lxc/list.go:317 lxc/remote.go:284
 msgid   "YES"
 msgstr  ""
 
@@ -963,11 +963,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:271
+#: lxc/remote.go:272
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:288
+#: lxc/remote.go:289
 msgid   "default"
 msgstr  ""
 
@@ -989,7 +989,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:291 lxc/image.go:538
+#: lxc/image.go:291 lxc/image.go:537
 msgid   "no"
 msgstr  ""
 
@@ -997,7 +997,7 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:156
+#: lxc/remote.go:157
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -1006,30 +1006,30 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:315
+#: lxc/remote.go:316
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:267 lxc/remote.go:311 lxc/remote.go:341 lxc/remote.go:352
+#: lxc/remote.go:268 lxc/remote.go:312 lxc/remote.go:342 lxc/remote.go:353
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:251
+#: lxc/remote.go:252
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/info.go:135
+#: lxc/info.go:139
 msgid   "stateful"
 msgstr  ""
 
-#: lxc/info.go:137
+#: lxc/info.go:141
 msgid   "stateless"
 msgstr  ""
 
-#: lxc/info.go:131
+#: lxc/info.go:135
 #, c-format
 msgid   "taken at %s"
 msgstr  ""
@@ -1042,7 +1042,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:294 lxc/image.go:542
+#: lxc/delete.go:45 lxc/image.go:294 lxc/image.go:541
 msgid   "yes"
 msgstr  ""
 
diff --git a/shared/container.go b/shared/container.go
index 438be1d..b7605b6 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -4,19 +4,48 @@ import (
 	"time"
 )
 
-type Ip struct {
-	Interface string `json:"interface"`
-	Protocol  string `json:"protocol"`
-	Address   string `json:"address"`
-	HostVeth  string `json:"host_veth"`
+type ContainerState struct {
+	Status     string                           `json:"status"`
+	StatusCode StatusCode                       `json:"status_code"`
+	Disk       map[string]ContainerStateDisk    `json:"disk"`
+	Memory     ContainerStateMemory             `json:"memory"`
+	Network    map[string]ContainerStateNetwork `json:"network"`
+	Pid        int64                            `json:"pid"`
+	Processes  int64                            `json:"processes"`
 }
 
-type ContainerState struct {
-	Status       string     `json:"status"`
-	StatusCode   StatusCode `json:"status_code"`
-	Init         int        `json:"init"`
-	Processcount int        `json:"processcount"`
-	Ips          []Ip       `json:"ips"`
+type ContainerStateDisk struct {
+	Usage int64 `json:"usage"`
+}
+
+type ContainerStateMemory struct {
+	Usage         int64 `json:"usage"`
+	UsagePeak     int64 `json:"usage_peak"`
+	SwapUsage     int64 `json:"swap_usage"`
+	SwapUsagePeak int64 `json:"swap_usage_peak"`
+}
+
+type ContainerStateNetwork struct {
+	Addresses []ContainerStateNetworkAddress `json:"addresses"`
+	Counters  ContainerStateNetworkCounters  `json:"counters"`
+	Hwaddr    string                         `json:"hwaddr"`
+	HostName  string                         `json:"host_name"`
+	Mtu       int                            `json:"mtu"`
+	State     string                         `json:"state"`
+	Type      string                         `json:"type"`
+}
+
+type ContainerStateNetworkAddress struct {
+	Family  string `json:"family"`
+	Address string `json:"address"`
+	Netmask string `json:"netmask"`
+}
+
+type ContainerStateNetworkCounters struct {
+	BytesReceived   int64 `json:"bytes_received"`
+	BytesSent       int64 `json:"bytes_sent"`
+	PacketsReceived int64 `json:"packets_received"`
+	PacketsSent     int64 `json:"packets_sent"`
 }
 
 type ContainerExecControl struct {
diff --git a/specs/rest-api.md b/specs/rest-api.md
index 7c793e3..fe4b4b5 100644
--- a/specs/rest-api.md
+++ b/specs/rest-api.md
@@ -620,38 +620,136 @@ HTTP code for this should be 202 (Accepted).
  * Return: dict representing current state
 
     {
-        "status": "Running",
-        "status_code": 103,
-        "init": 16126,
-        "processcount": 7,
-        "ips": [
-            {
-                "interface": "eth0",
-                "protocol": "IPV4",
-                "address": "172.17.0.242",
-                "host_veth": ""
+        "type": "sync",
+        "status": "Success",
+        "status_code": 200,
+        "metadata": {
+            "status": "Running",
+            "status_code": 103,
+            "disk": {
+                "root": {
+                    "usage": 422330368
+                }
             },
-            {
-                "interface": "eth0",
-                "protocol": "IPV6",
-                "address": "2607:f2c0:f00f:2700:216:3eff:fe1c:9438",
-                "host_veth": ""
+            "memory": {
+                "usage": 51126272,
+                "usage_peak": 70246400,
+                "swap_usage": 0,
+                "swap_usage_peak": 0
             },
-            {
-                "interface": "lo",
-                "protocol": "IPV4",
-                "address": "127.0.0.1",
-                "host_veth": ""
+            "network": {
+                "eth0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "10.0.3.27",
+                            "netmask": "24"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::216:3eff:feec:65a8",
+                            "netmask": "64"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 33942,
+                        "bytes_sent": 30810,
+                        "packets_received": 402,
+                        "packets_sent": 178
+                    },
+                    "hwaddr": "00:16:3e:ec:65:a8",
+                    "host_name": "vethBWTSU5",
+                    "mtu": 1500,
+                    "state": "up",
+                    "type": "broadcast"
+                },
+                "lo": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "127.0.0.1",
+                            "netmask": "8"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "::1",
+                            "netmask": "128"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 86816,
+                        "bytes_sent": 86816,
+                        "packets_received": 1226,
+                        "packets_sent": 1226
+                    },
+                    "hwaddr": "",
+                    "host_name": "",
+                    "mtu": 65536,
+                    "state": "up",
+                    "type": "loopback"
+                },
+                "lxcbr0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "10.0.3.1",
+                            "netmask": "24"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::68d4:87ff:fe40:7769",
+                            "netmask": "64"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 0,
+                        "bytes_sent": 570,
+                        "packets_received": 0,
+                        "packets_sent": 7
+                    },
+                    "hwaddr": "6a:d4:87:40:77:69",
+                    "host_name": "",
+                    "mtu": 1500,
+                    "state": "up",
+                    "type": "broadcast"
+               },
+               "zt0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "29.17.181.59",
+                            "netmask": "7"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fd80:56c2:e21c:0:199:9379:e711:b3e1",
+                            "netmask": "88"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::79:e7ff:fe0d:5123",
+                            "netmask": "64"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 0,
+                        "bytes_sent": 806,
+                        "packets_received": 0,
+                        "packets_sent": 9
+                    },
+                    "hwaddr": "02:79:e7:0d:51:23",
+                    "host_name": "",
+                    "mtu": 2800,
+                    "state": "up",
+                    "type": "broadcast"
+                }
             },
-            {
-                "interface": "lo",
-                "protocol": "IPV6",
-                "address": "::1",
-                "host_veth": ""
-            }
-        ]
+            "pid": 13663,
+            "processes": 32
+        }
     }
 
+
 ### PUT
  * Description: change the container state
  * Authentication: trusted
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 6161f63..29d4720 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -241,12 +241,12 @@ test_basic_usage() {
   # Ephemeral
   lxc launch testimage foo -e
 
-  OLD_INIT=$(lxc info foo | grep ^Init)
+  OLD_INIT=$(lxc info foo | grep ^Pid)
   lxc exec foo reboot
 
   # shellcheck disable=SC2034
   for i in $(seq 10); do
-    NEW_INIT=$(lxc info foo | grep ^Init || true)
+    NEW_INIT=$(lxc info foo | grep ^Pid || true)
 
     if [ -n "${NEW_INIT}" ] && [ "${OLD_INIT}" != "${NEW_INIT}" ]; then
       break


More information about the lxc-devel mailing list