[lxc-devel] [go-lxc/v2] Fix the locking logic (v2)

caglar10ur on Github lxc-bot at linuxcontainers.org
Fri Nov 3 06:50:21 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 963 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171103/1dc6356f/attachment.bin>
-------------- next part --------------
From e2aed2b57d9a67df7f89727ee2baecd1d7eccf9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=2E=C3=87a=C4=9Flar=20Onur?= <caglar at 10ur.org>
Date: Thu, 2 Nov 2017 23:39:42 -0700
Subject: [PATCH] Fix the locking logic

The original code where we were doing following

func X()  {
    call makesure
	makesure locks
	...work
	makesure unlocks
    lock
    ...work
    unlock
}

was potentially racy as someone else could obtain the lock in between
makesure and mutex.Lock call.

Changes since last attempt:

- Running LXD tests locally ends up with success.

...
==> Deleting all storage pools
Storage pool lxdtest-cjU deleted
==> Checking for locked DB tables
==> Checking for leftover files
==> Checking for leftover DB entries
==> Tearing down directory backend in
/home/caglar/go/src/github.com/lxc/lxd/test/tmp.IQA/cjU

==> Test result: success
...
---
 Makefile     |   4 +
 container.go | 525 +++++++++++++++++++++++++++++++++++++----------------------
 lxc_test.go  |  11 ++
 3 files changed, 348 insertions(+), 192 deletions(-)

diff --git a/Makefile b/Makefile
index 444a49b..149ce66 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,10 @@ escape-analysis:
 ctags:
 	@ctags -R --languages=c,go
 
+scope:
+	@echo "$(OK_COLOR)==> Exported container calls in container.go $(NO_COLOR)"
+	@/bin/grep -E "\bc+\.([A-Z])\w+" container.go || true
+
 setup-test-cgroup:
 	for d in /sys/fs/cgroup/*; do \
 	    [ -f $$d/cgroup.clone_children ] && echo 1 | sudo tee $$d/cgroup.clone_children; \
diff --git a/container.go b/container.go
index f859d3d..af5485a 100644
--- a/container.go
+++ b/container.go
@@ -16,6 +16,7 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"reflect"
 	"strconv"
@@ -53,20 +54,20 @@ const (
 )
 
 func (c *Container) makeSure(flags int) error {
-	if flags&isDefined != 0 && !c.Defined() {
-		return ErrNotDefined
+	if flags&isDefined != 0 && !c.defined() {
+		return fmt.Errorf("%s: %q", ErrNotDefined, c.name())
 	}
 
-	if flags&isNotDefined != 0 && c.Defined() {
-		return ErrAlreadyDefined
+	if flags&isNotDefined != 0 && c.defined() {
+		return fmt.Errorf("%s: %q", ErrAlreadyDefined, c.name())
 	}
 
-	if flags&isRunning != 0 && !c.Running() {
-		return ErrNotRunning
+	if flags&isRunning != 0 && !c.running() {
+		return fmt.Errorf("%s: %q", ErrNotRunning, c.name())
 	}
 
-	if flags&isNotRunning != 0 && c.Running() {
-		return ErrAlreadyRunning
+	if flags&isNotRunning != 0 && c.running() {
+		return fmt.Errorf("%s: %q", ErrAlreadyRunning, c.name())
 	}
 
 	if flags&isPrivileged != 0 && os.Geteuid() != 0 {
@@ -85,7 +86,7 @@ func (c *Container) makeSure(flags int) error {
 }
 
 func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSize, error) {
-	size, err := strconv.ParseFloat(c.CgroupItem(filename)[0], 64)
+	size, err := strconv.ParseFloat(c.cgroupItem(filename)[0], 64)
 	if err != nil {
 		return -1, missing
 	}
@@ -93,18 +94,35 @@ func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSi
 }
 
 func (c *Container) setCgroupItemWithByteSize(filename string, limit ByteSize, missing error) error {
-	if err := c.SetCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil {
+	if err := c.setCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil {
 		return missing
 	}
 	return nil
 }
 
+func (c *Container) name() string {
+	return C.GoString(c.container.name)
+}
+
 // Name returns the name of the container.
 func (c *Container) Name() string {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	return C.GoString(c.container.name)
+	return c.name()
+}
+
+// String returns the string represantation of container.
+func (c *Container) String() string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
+	return path.Join(c.configPath(), c.name())
+}
+
+// Caller needs to hold the lock
+func (c *Container) defined() bool {
+	return bool(C.go_lxc_defined(c.container))
 }
 
 // Defined returns true if the container is already defined.
@@ -112,7 +130,12 @@ func (c *Container) Defined() bool {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	return bool(C.go_lxc_defined(c.container))
+	return c.defined()
+}
+
+// Caller needs to hold the lock
+func (c *Container) running() bool {
+	return bool(C.go_lxc_running(c.container))
 }
 
 // Running returns true if the container is already running.
@@ -120,7 +143,7 @@ func (c *Container) Running() bool {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	return bool(C.go_lxc_running(c.container))
+	return c.running()
 }
 
 // Controllable returns true if the caller can control the container.
@@ -133,13 +156,13 @@ func (c *Container) Controllable() bool {
 
 // CreateSnapshot creates a new snapshot.
 func (c *Container) CreateSnapshot() (*Snapshot, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined | isNotRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	ret := int(C.go_lxc_snapshot(c.container))
 	if ret < 0 {
 		return nil, ErrCreateSnapshotFailed
@@ -149,13 +172,13 @@ func (c *Container) CreateSnapshot() (*Snapshot, error) {
 
 // RestoreSnapshot creates a new container based on a snapshot.
 func (c *Container) RestoreSnapshot(snapshot Snapshot, name string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
@@ -170,13 +193,13 @@ func (c *Container) RestoreSnapshot(snapshot Snapshot, name string) error {
 
 // DestroySnapshot destroys the specified snapshot.
 func (c *Container) DestroySnapshot(snapshot Snapshot) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csnapname := C.CString(snapshot.Name)
 	defer C.free(unsafe.Pointer(csnapname))
 
@@ -188,13 +211,13 @@ func (c *Container) DestroySnapshot(snapshot Snapshot) error {
 
 // DestroyAllSnapshots destroys all the snapshot.
 func (c *Container) DestroyAllSnapshots() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_snapshot_destroy_all(c.container)) {
 		return ErrDestroyAllSnapshotsFailed
 	}
@@ -203,13 +226,13 @@ func (c *Container) DestroyAllSnapshots() error {
 
 // Snapshots returns the list of container snapshots.
 func (c *Container) Snapshots() ([]Snapshot, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isDefined); err != nil {
 		return nil, err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	var csnapshots *C.struct_lxc_snapshot
 
 	size := int(C.go_lxc_snapshot_list(c.container, &csnapshots))
@@ -239,12 +262,17 @@ func (c *Container) Snapshots() ([]Snapshot, error) {
 	return snapshots, nil
 }
 
+// Caller needs to hold the lock
+func (c *Container) state() State {
+	return StateMap[C.GoString(C.go_lxc_state(c.container))]
+}
+
 // State returns the state of the container.
 func (c *Container) State() State {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	return StateMap[C.GoString(C.go_lxc_state(c.container))]
+	return c.state()
 }
 
 // InitPid returns the process ID of the container's init process
@@ -297,17 +325,14 @@ func (c *Container) SetVerbosity(verbosity Verbosity) {
 
 // Freeze freezes the running container.
 func (c *Container) Freeze() error {
-	if err := c.makeSure(isRunning); err != nil {
-		return err
-	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
 
-	if c.State() == FROZEN {
+	// check the state using lockless version
+	if c.state() == FROZEN {
 		return ErrAlreadyFrozen
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_freeze(c.container)) {
 		return ErrFreezeFailed
 	}
@@ -317,17 +342,18 @@ func (c *Container) Freeze() error {
 
 // Unfreeze thaws the frozen container.
 func (c *Container) Unfreeze() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
 
-	if c.State() != FROZEN {
+	// check the state using lockless version
+	if c.state() != FROZEN {
 		return ErrNotFrozen
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_unfreeze(c.container)) {
 		return ErrUnfreezeFailed
 	}
@@ -337,6 +363,9 @@ func (c *Container) Unfreeze() error {
 
 // Create creates the container using given TemplateOptions
 func (c *Container) Create(options TemplateOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	// FIXME: Support bdev_specs
 	//
 	// bdev_specs:
@@ -348,9 +377,6 @@ func (c *Container) Create(options TemplateOptions) error {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	// use download template if not set
 	if options.Template == "" {
 		options.Template = "download"
@@ -435,13 +461,13 @@ func (c *Container) Create(options TemplateOptions) error {
 
 // Start starts the container.
 func (c *Container) Start() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isNotRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_start(c.container, 0, nil)) {
 		return ErrStartFailed
 	}
@@ -450,13 +476,13 @@ func (c *Container) Start() error {
 
 // StartWithArgs starts the container using given arguments.
 func (c *Container) StartWithArgs(args []string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isNotRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_start(c.container, 0, makeNullTerminatedArgs(args))) {
 		return ErrStartFailed
 	}
@@ -465,23 +491,23 @@ func (c *Container) StartWithArgs(args []string) error {
 
 // Execute executes the given command in a temporary container.
 func (c *Container) Execute(args ...string) ([]byte, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isNotDefined); err != nil {
 		return nil, err
 	}
 
 	// Deal with LXC 2.1's need of a defined container
 	if VersionAtLeast(2, 1, 0) {
-		os.MkdirAll(filepath.Join(c.ConfigPath(), c.Name()), 0700)
-		c.SaveConfigFile(filepath.Join(c.ConfigPath(), c.Name(), "config"))
-		defer os.RemoveAll(filepath.Join(c.ConfigPath(), c.Name()))
+		os.MkdirAll(filepath.Join(c.configPath(), c.name()), 0700)
+		c.saveConfigFile(filepath.Join(c.configPath(), c.name(), "config"))
+		defer os.RemoveAll(filepath.Join(c.configPath(), c.name()))
 	}
 
-	cargs := []string{"lxc-execute", "-n", c.Name(), "-P", c.ConfigPath(), "--"}
+	cargs := []string{"lxc-execute", "-n", c.name(), "-P", c.configPath(), "--"}
 	cargs = append(cargs, args...)
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	// FIXME: Go runtime and src/lxc/start.c signal_handler are not playing nice together so use lxc-execute for now
 	// go-nuts thread: https://groups.google.com/forum/#!msg/golang-nuts/h9GbvfYv83w/5Ly_jvOr86wJ
 	output, err := exec.Command(cargs[0], cargs[1:]...).CombinedOutput()
@@ -506,13 +532,13 @@ func (c *Container) Execute(args ...string) ([]byte, error) {
 
 // Stop stops the container.
 func (c *Container) Stop() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_stop(c.container)) {
 		return ErrStopFailed
 	}
@@ -521,13 +547,13 @@ func (c *Container) Stop() error {
 
 // Reboot reboots the container.
 func (c *Container) Reboot() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_reboot(c.container)) {
 		return ErrRebootFailed
 	}
@@ -536,11 +562,12 @@ func (c *Container) Reboot() error {
 
 // Shutdown shuts down the container.
 func (c *Container) Shutdown(timeout time.Duration) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
-	c.mu.Lock()
-	defer c.mu.Unlock()
 
 	if !bool(C.go_lxc_shutdown(c.container, C.int(timeout.Seconds()))) {
 		return ErrShutdownFailed
@@ -550,13 +577,13 @@ func (c *Container) Shutdown(timeout time.Duration) error {
 
 // Destroy destroys the container.
 func (c *Container) Destroy() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined | isNotRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_destroy(c.container)) {
 		return ErrDestroyFailed
 	}
@@ -565,13 +592,13 @@ func (c *Container) Destroy() error {
 
 // DestroyWithAllSnapshots destroys the container and its snapshots
 func (c *Container) DestroyWithAllSnapshots() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined | isNotRunning | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	if !bool(C.go_lxc_destroy_with_snapshots(c.container)) {
 		return ErrDestroyWithAllSnapshotsFailed
 	}
@@ -580,6 +607,9 @@ func (c *Container) DestroyWithAllSnapshots() error {
 
 // Clone clones the container using given arguments with specified backend.
 func (c *Container) Clone(name string, options CloneOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	// FIXME: bdevdata, newsize and hookargs
 	//
 	// bdevdata:
@@ -595,9 +625,6 @@ func (c *Container) Clone(name string, options CloneOptions) error {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	// use Directory backend if not set
 	if options.Backend == 0 {
 		options.Backend = Directory
@@ -637,13 +664,13 @@ func (c *Container) Clone(name string, options CloneOptions) error {
 
 // Rename renames the container.
 func (c *Container) Rename(name string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isDefined | isNotRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
@@ -676,11 +703,7 @@ func (c *Container) ConfigFileName() string {
 	return C.GoString(configFileName)
 }
 
-// ConfigItem returns the value of the given config item.
-func (c *Container) ConfigItem(key string) []string {
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
+func (c *Container) configItem(key string) []string {
 	ckey := C.CString(key)
 	defer C.free(unsafe.Pointer(ckey))
 
@@ -692,11 +715,15 @@ func (c *Container) ConfigItem(key string) []string {
 	return strings.Split(ret, "\n")
 }
 
-// SetConfigItem sets the value of the given config item.
-func (c *Container) SetConfigItem(key string, value string) error {
-	c.mu.Lock()
-	defer c.mu.Unlock()
+// ConfigItem returns the value of the given config item.
+func (c *Container) ConfigItem(key string) []string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
+	return c.configItem(key)
+}
 
+func (c *Container) setConfigItem(key string, value string) error {
 	ckey := C.CString(key)
 	defer C.free(unsafe.Pointer(ckey))
 
@@ -709,11 +736,15 @@ func (c *Container) SetConfigItem(key string, value string) error {
 	return nil
 }
 
-// RunningConfigItem returns the value of the given config item.
-func (c *Container) RunningConfigItem(key string) []string {
-	c.mu.RLock()
-	defer c.mu.RUnlock()
+// SetConfigItem sets the value of the given config item.
+func (c *Container) SetConfigItem(key string, value string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
 
+	return c.setConfigItem(key, value)
+}
+
+func (c *Container) runningConfigItem(key string) []string {
 	ckey := C.CString(key)
 	defer C.free(unsafe.Pointer(ckey))
 
@@ -725,11 +756,15 @@ func (c *Container) RunningConfigItem(key string) []string {
 	return strings.Split(ret, "\n")
 }
 
-// CgroupItem returns the value of the given cgroup subsystem value.
-func (c *Container) CgroupItem(key string) []string {
+// RunningConfigItem returns the value of the given config item.
+func (c *Container) RunningConfigItem(key string) []string {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
+	return c.runningConfigItem(key)
+}
+
+func (c *Container) cgroupItem(key string) []string {
 	ckey := C.CString(key)
 	defer C.free(unsafe.Pointer(ckey))
 
@@ -741,11 +776,7 @@ func (c *Container) CgroupItem(key string) []string {
 	return strings.Split(ret, "\n")
 }
 
-// SetCgroupItem sets the value of given cgroup subsystem value.
-func (c *Container) SetCgroupItem(key string, value string) error {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
+func (c *Container) setCgroupItem(key string, value string) error {
 	ckey := C.CString(key)
 	defer C.free(unsafe.Pointer(ckey))
 
@@ -758,6 +789,22 @@ func (c *Container) SetCgroupItem(key string, value string) error {
 	return nil
 }
 
+// CgroupItem returns the value of the given cgroup subsystem value.
+func (c *Container) CgroupItem(key string) []string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
+	return c.cgroupItem(key)
+}
+
+// SetCgroupItem sets the value of given cgroup subsystem value.
+func (c *Container) SetCgroupItem(key string, value string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	return c.setCgroupItem(key, value)
+}
+
 // ClearConfig completely clears the containers in-memory configuration.
 func (c *Container) ClearConfig() {
 	c.mu.Lock()
@@ -817,11 +864,7 @@ func (c *Container) LoadConfigFile(path string) error {
 	return nil
 }
 
-// SaveConfigFile saves the configuration file to given path.
-func (c *Container) SaveConfigFile(path string) error {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
+func (c *Container) saveConfigFile(path string) error {
 	cpath := C.CString(path)
 	defer C.free(unsafe.Pointer(cpath))
 
@@ -831,12 +874,24 @@ func (c *Container) SaveConfigFile(path string) error {
 	return nil
 }
 
+// SaveConfigFile saves the configuration file to given path.
+func (c *Container) SaveConfigFile(path string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	return c.saveConfigFile(path)
+}
+
+func (c *Container) configPath() string {
+	return C.GoString(C.go_lxc_get_config_path(c.container))
+}
+
 // ConfigPath returns the configuration file's path.
 func (c *Container) ConfigPath() string {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 
-	return C.GoString(C.go_lxc_get_config_path(c.container))
+	return c.configPath()
 }
 
 // SetConfigPath sets the configuration file's path.
@@ -855,6 +910,9 @@ func (c *Container) SetConfigPath(path string) error {
 
 // MemoryUsage returns memory usage of the container in bytes.
 func (c *Container) MemoryUsage() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -864,6 +922,9 @@ func (c *Container) MemoryUsage() (ByteSize, error) {
 
 // MemoryLimit returns memory limit of the container in bytes.
 func (c *Container) MemoryLimit() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -873,6 +934,9 @@ func (c *Container) MemoryLimit() (ByteSize, error) {
 
 // SetMemoryLimit sets memory limit of the container in bytes.
 func (c *Container) SetMemoryLimit(limit ByteSize) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
@@ -882,6 +946,9 @@ func (c *Container) SetMemoryLimit(limit ByteSize) error {
 
 // SoftMemoryLimit returns soft memory limit of the container in bytes.
 func (c *Container) SoftMemoryLimit() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -891,6 +958,9 @@ func (c *Container) SoftMemoryLimit() (ByteSize, error) {
 
 // SetSoftMemoryLimit sets soft  memory limit of the container in bytes.
 func (c *Container) SetSoftMemoryLimit(limit ByteSize) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
@@ -900,6 +970,9 @@ func (c *Container) SetSoftMemoryLimit(limit ByteSize) error {
 
 // KernelMemoryUsage returns current kernel memory allocation of the container in bytes.
 func (c *Container) KernelMemoryUsage() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -909,6 +982,9 @@ func (c *Container) KernelMemoryUsage() (ByteSize, error) {
 
 // KernelMemoryLimit returns kernel memory limit of the container in bytes.
 func (c *Container) KernelMemoryLimit() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -918,6 +994,9 @@ func (c *Container) KernelMemoryLimit() (ByteSize, error) {
 
 // SetKernelMemoryLimit sets kernel memory limit of the container in bytes.
 func (c *Container) SetKernelMemoryLimit(limit ByteSize) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
@@ -927,6 +1006,9 @@ func (c *Container) SetKernelMemoryLimit(limit ByteSize) error {
 
 // MemorySwapUsage returns memory+swap usage of the container in bytes.
 func (c *Container) MemorySwapUsage() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -936,6 +1018,9 @@ func (c *Container) MemorySwapUsage() (ByteSize, error) {
 
 // MemorySwapLimit returns the memory+swap limit of the container in bytes.
 func (c *Container) MemorySwapLimit() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
@@ -945,6 +1030,9 @@ func (c *Container) MemorySwapLimit() (ByteSize, error) {
 
 // SetMemorySwapLimit sets memory+swap limit of the container in bytes.
 func (c *Container) SetMemorySwapLimit(limit ByteSize) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
@@ -954,14 +1042,19 @@ func (c *Container) SetMemorySwapLimit(limit ByteSize) error {
 
 // BlkioUsage returns number of bytes transferred to/from the disk by the container.
 func (c *Container) BlkioUsage() (ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
+	ioServiceBytes := c.cgroupItem("blkio.throttle.io_service_bytes")
+	if ioServiceBytes[0] == "" {
+		return 0, nil
+	}
 
-	for _, v := range c.CgroupItem("blkio.throttle.io_service_bytes") {
+	for _, v := range ioServiceBytes {
 		b := strings.Split(v, " ")
 		if b[0] == "Total" {
 			blkioUsed, err := strconv.ParseFloat(b[1], 64)
@@ -977,14 +1070,19 @@ func (c *Container) BlkioUsage() (ByteSize, error) {
 // CPUTime returns the total CPU time (in nanoseconds) consumed by all tasks
 // in this cgroup (including tasks lower in the hierarchy).
 func (c *Container) CPUTime() (time.Duration, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
+	usage := c.cgroupItem("cpuacct.usage")
+	if usage[0] == "" {
+		return 0, nil
+	}
 
-	cpuUsage, err := strconv.ParseInt(c.CgroupItem("cpuacct.usage")[0], 10, 64)
+	cpuUsage, err := strconv.ParseInt(usage[0], 10, 64)
 	if err != nil {
 		return -1, err
 	}
@@ -994,15 +1092,20 @@ func (c *Container) CPUTime() (time.Duration, error) {
 // CPUTimePerCPU returns the CPU time (in nanoseconds) consumed on each CPU by
 // all tasks in this cgroup (including tasks lower in the hierarchy).
 func (c *Container) CPUTimePerCPU() (map[int]time.Duration, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
+	usagePerCPU := c.cgroupItem("cpuacct.usage_percpu")
+	if usagePerCPU[0] == "" {
+		return map[int]time.Duration{0: 0}, nil
+	}
 
 	cpuTimes := make(map[int]time.Duration)
-	for i, v := range strings.Split(c.CgroupItem("cpuacct.usage_percpu")[0], " ") {
+	for i, v := range strings.Split(usagePerCPU[0], " ") {
 		cpuUsage, err := strconv.ParseInt(v, 10, 64)
 		if err != nil {
 			return nil, err
@@ -1015,19 +1118,23 @@ func (c *Container) CPUTimePerCPU() (map[int]time.Duration, error) {
 // CPUStats returns the number of CPU cycles (in the units defined by USER_HZ on the system)
 // consumed by tasks in this cgroup and its children in both user mode and system (kernel) mode.
 func (c *Container) CPUStats() (map[string]int64, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
+	stat := c.cgroupItem("cpuacct.stat")
+	if stat[0] == "" {
+		return map[string]int64{"user": 0, "system": 0}, nil
+	}
 
-	cpuStat := c.CgroupItem("cpuacct.stat")
-	user, err := strconv.ParseInt(strings.Split(cpuStat[0], "user ")[1], 10, 64)
+	user, err := strconv.ParseInt(strings.Split(stat[0], "user ")[1], 10, 64)
 	if err != nil {
 		return nil, err
 	}
-	system, err := strconv.ParseInt(strings.Split(cpuStat[1], "system ")[1], 10, 64)
+	system, err := strconv.ParseInt(strings.Split(stat[1], "system ")[1], 10, 64)
 	if err != nil {
 		return nil, err
 	}
@@ -1043,14 +1150,14 @@ func (c *Container) CPUStats() (map[string]int64, error) {
 // indicate that it is done with the allocated console so that it can
 // be allocated by another caller.
 func (c *Container) ConsoleFd(ttynum int) (int, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	// FIXME: Make idiomatic
 	if err := c.makeSure(isRunning); err != nil {
 		return -1, err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	ret := int(C.go_lxc_console_getfd(c.container, C.int(ttynum)))
 	if ret < 0 {
 		return -1, ErrAttachFailed
@@ -1062,13 +1169,13 @@ func (c *Container) ConsoleFd(ttynum int) (int, error) {
 //
 // This function will not return until the console has been exited by the user.
 func (c *Container) Console(options ConsoleOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	ret := bool(C.go_lxc_console(c.container,
 		C.int(options.Tty),
 		C.int(options.StdinFd),
@@ -1085,13 +1192,13 @@ func (c *Container) Console(options ConsoleOptions) error {
 // AttachShell attaches a shell to the container.
 // It clears all environment variables before attaching.
 func (c *Container) AttachShell(options AttachOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	cenv := makeNullTerminatedArgs(options.Env)
 	if cenv == nil {
 		return ErrAllocationFailed
@@ -1126,11 +1233,7 @@ func (c *Container) AttachShell(options AttachOptions) error {
 	return nil
 }
 
-// RunCommandStatus attachs a shell and runs the command within the container.
-// The process will wait for the command to finish and return the result of
-// waitpid(), i.e. the process' exit status. An error is returned only when
-// invocation of the command completely fails.
-func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int, error) {
+func (c *Container) runCommandStatus(args []string, options AttachOptions) (int, error) {
 	if len(args) == 0 {
 		return -1, ErrInsufficientNumberOfArguments
 	}
@@ -1139,9 +1242,6 @@ func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int,
 		return -1, err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	cargs := makeNullTerminatedArgs(args)
 	if cargs == nil {
 		return -1, ErrAllocationFailed
@@ -1182,8 +1282,22 @@ func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int,
 	return ret, nil
 }
 
+// RunCommandStatus attachs a shell and runs the command within the container.
+// The process will wait for the command to finish and return the result of
+// waitpid(), i.e. the process' exit status. An error is returned only when
+// invocation of the command completely fails.
+func (c *Container) RunCommandStatus(args []string, options AttachOptions) (int, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	return c.runCommandStatus(args, options)
+}
+
 // RunCommandNoWait runs the given command and returns without waiting it to finish.
 func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if len(args) == 0 {
 		return -1, ErrInsufficientNumberOfArguments
 	}
@@ -1192,9 +1306,6 @@ func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int,
 		return -1, err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	cargs := makeNullTerminatedArgs(args)
 	if cargs == nil {
 		return -1, ErrAllocationFailed
@@ -1245,7 +1356,10 @@ func (c *Container) RunCommandNoWait(args []string, options AttachOptions) (int,
 // The process will wait for the command to finish and return a success status. An error
 // is returned only when invocation of the command completely fails.
 func (c *Container) RunCommand(args []string, options AttachOptions) (bool, error) {
-	ret, err := c.RunCommandStatus(args, options)
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	ret, err := c.runCommandStatus(args, options)
 	if err != nil {
 		return false, err
 	}
@@ -1257,13 +1371,13 @@ func (c *Container) RunCommand(args []string, options AttachOptions) (bool, erro
 
 // Interfaces returns the names of the network interfaces.
 func (c *Container) Interfaces() ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	result := C.go_lxc_get_interfaces(c.container)
 	if result == nil {
 		return nil, ErrInterfaces
@@ -1273,13 +1387,13 @@ func (c *Container) Interfaces() ([]string, error) {
 
 // InterfaceStats returns the stats about container's network interfaces
 func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	var interfaceName string
 
 	statistics := make(map[string]map[string]ByteSize)
@@ -1289,16 +1403,16 @@ func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
 		netPrefix = "lxc.network"
 	}
 
-	for i := 0; i < len(c.ConfigItem(netPrefix)); i++ {
-		interfaceType := c.RunningConfigItem(fmt.Sprintf("%s.%d.type", netPrefix, i))
+	for i := 0; i < len(c.configItem(netPrefix)); i++ {
+		interfaceType := c.runningConfigItem(fmt.Sprintf("%s.%d.type", netPrefix, i))
 		if interfaceType == nil {
 			continue
 		}
 
 		if interfaceType[0] == "veth" {
-			interfaceName = c.RunningConfigItem(fmt.Sprintf("%s.%d.veth.pair", netPrefix, i))[0]
+			interfaceName = c.runningConfigItem(fmt.Sprintf("%s.%d.veth.pair", netPrefix, i))[0]
 		} else {
-			interfaceName = c.RunningConfigItem(fmt.Sprintf("%s.%d.link", netPrefix, i))[0]
+			interfaceName = c.runningConfigItem(fmt.Sprintf("%s.%d.link", netPrefix, i))[0]
 		}
 
 		for _, v := range []string{"rx", "tx"} {
@@ -1325,13 +1439,13 @@ func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
 
 // IPAddress returns the IP address of the given network interface.
 func (c *Container) IPAddress(interfaceName string) ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	cinterface := C.CString(interfaceName)
 	defer C.free(unsafe.Pointer(cinterface))
 
@@ -1344,13 +1458,13 @@ func (c *Container) IPAddress(interfaceName string) ([]string, error) {
 
 // IPv4Address returns the IPv4 address of the given network interface.
 func (c *Container) IPv4Address(interfaceName string) ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	cinterface := C.CString(interfaceName)
 	defer C.free(unsafe.Pointer(cinterface))
 
@@ -1366,13 +1480,13 @@ func (c *Container) IPv4Address(interfaceName string) ([]string, error) {
 
 // IPv6Address returns the IPv6 address of the given network interface.
 func (c *Container) IPv6Address(interfaceName string) ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	cinterface := C.CString(interfaceName)
 	defer C.free(unsafe.Pointer(cinterface))
 
@@ -1388,9 +1502,12 @@ func (c *Container) IPv6Address(interfaceName string) ([]string, error) {
 
 // WaitIPAddresses waits until IPAddresses call returns something or time outs
 func (c *Container) WaitIPAddresses(timeout time.Duration) ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	now := time.Now()
 	for {
-		if result, err := c.IPAddresses(); err == nil && len(result) > 0 {
+		if result, err := c.ipAddresses(); err == nil && len(result) > 0 {
 			return result, nil
 		}
 		// Python API sleeps 1 second as well
@@ -1402,15 +1519,11 @@ func (c *Container) WaitIPAddresses(timeout time.Duration) ([]string, error) {
 	}
 }
 
-// IPAddresses returns all IP addresses.
-func (c *Container) IPAddresses() ([]string, error) {
+func (c *Container) ipAddresses() ([]string, error) {
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	result := C.go_lxc_get_ips(c.container, nil, nil, 0)
 	if result == nil {
 		return nil, ErrIPAddresses
@@ -1419,15 +1532,23 @@ func (c *Container) IPAddresses() ([]string, error) {
 
 }
 
+// IPAddresses returns all IP addresses.
+func (c *Container) IPAddresses() ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
+	return c.ipAddresses()
+}
+
 // IPv4Addresses returns all IPv4 addresses.
 func (c *Container) IPv4Addresses() ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	cfamily := C.CString("inet")
 	defer C.free(unsafe.Pointer(cfamily))
 
@@ -1440,13 +1561,13 @@ func (c *Container) IPv4Addresses() ([]string, error) {
 
 // IPv6Addresses returns all IPv6 addresses.
 func (c *Container) IPv6Addresses() ([]string, error) {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if err := c.makeSure(isRunning); err != nil {
 		return nil, err
 	}
 
-	c.mu.RLock()
-	defer c.mu.RUnlock()
-
 	cfamily := C.CString("inet6")
 	defer C.free(unsafe.Pointer(cfamily))
 
@@ -1459,20 +1580,26 @@ func (c *Container) IPv6Addresses() ([]string, error) {
 
 // LogFile returns the name of the logfile.
 func (c *Container) LogFile() string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if VersionAtLeast(2, 1, 0) {
-		return c.ConfigItem("lxc.log.file")[0]
+		return c.configItem("lxc.log.file")[0]
 	}
 
-	return c.ConfigItem("lxc.logfile")[0]
+	return c.configItem("lxc.logfile")[0]
 }
 
 // SetLogFile sets the name of the logfile.
 func (c *Container) SetLogFile(filename string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	var err error
 	if VersionAtLeast(2, 1, 0) {
-		err = c.SetConfigItem("lxc.log.file", filename)
+		err = c.setConfigItem("lxc.log.file", filename)
 	} else {
-		err = c.SetConfigItem("lxc.logfile", filename)
+		err = c.setConfigItem("lxc.logfile", filename)
 	}
 	if err != nil {
 		return err
@@ -1483,20 +1610,26 @@ func (c *Container) SetLogFile(filename string) error {
 
 // LogLevel returns the level of the logfile.
 func (c *Container) LogLevel() LogLevel {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+
 	if VersionAtLeast(2, 1, 0) {
-		return logLevelMap[c.ConfigItem("lxc.log.level")[0]]
+		return logLevelMap[c.configItem("lxc.log.level")[0]]
 	}
 
-	return logLevelMap[c.ConfigItem("lxc.loglevel")[0]]
+	return logLevelMap[c.configItem("lxc.loglevel")[0]]
 }
 
 // SetLogLevel sets the level of the logfile.
 func (c *Container) SetLogLevel(level LogLevel) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	var err error
 	if VersionAtLeast(2, 1, 0) {
-		err = c.SetConfigItem("lxc.log.level", level.String())
+		err = c.setConfigItem("lxc.log.level", level.String())
 	} else {
-		err = c.SetConfigItem("lxc.loglevel", level.String())
+		err = c.setConfigItem("lxc.loglevel", level.String())
 	}
 	if err != nil {
 		return err
@@ -1506,13 +1639,13 @@ func (c *Container) SetLogLevel(level LogLevel) error {
 
 // AddDeviceNode adds specified device to the container.
 func (c *Container) AddDeviceNode(source string, destination ...string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isPrivileged); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csource := C.CString(source)
 	defer C.free(unsafe.Pointer(csource))
 
@@ -1530,18 +1663,17 @@ func (c *Container) AddDeviceNode(source string, destination ...string) error {
 		return ErrAddDeviceNodeFailed
 	}
 	return nil
-
 }
 
 // RemoveDeviceNode removes the specified device from the container.
 func (c *Container) RemoveDeviceNode(source string, destination ...string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isPrivileged); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csource := C.CString(source)
 	defer C.free(unsafe.Pointer(csource))
 
@@ -1563,6 +1695,9 @@ func (c *Container) RemoveDeviceNode(source string, destination ...string) error
 
 // Checkpoint checkpoints the container.
 func (c *Container) Checkpoint(opts CheckpointOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
@@ -1581,6 +1716,9 @@ func (c *Container) Checkpoint(opts CheckpointOptions) error {
 
 // Restore restores the container from a checkpoint.
 func (c *Container) Restore(opts RestoreOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
@@ -1598,6 +1736,9 @@ func (c *Container) Restore(opts RestoreOptions) error {
 
 // Migrate migrates the container.
 func (c *Container) Migrate(cmd uint, opts MigrateOptions) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isNotDefined | isGreaterEqualThanLXC20); err != nil {
 		return err
 	}
@@ -1650,13 +1791,13 @@ func (c *Container) Migrate(cmd uint, opts MigrateOptions) error {
 
 // AttachInterface attaches specifed netdev to the container.
 func (c *Container) AttachInterface(source, destination string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csource := C.CString(source)
 	defer C.free(unsafe.Pointer(csource))
 
@@ -1671,13 +1812,13 @@ func (c *Container) AttachInterface(source, destination string) error {
 
 // DetachInterface detaches specifed netdev from the container.
 func (c *Container) DetachInterface(source string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csource := C.CString(source)
 	defer C.free(unsafe.Pointer(csource))
 
@@ -1689,13 +1830,13 @@ func (c *Container) DetachInterface(source string) error {
 
 // DetachInterfaceRename detaches specifed netdev from the container and renames it.
 func (c *Container) DetachInterfaceRename(source, target string) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
 	if err := c.makeSure(isRunning | isPrivileged | isGreaterEqualThanLXC11); err != nil {
 		return err
 	}
 
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
 	csource := C.CString(source)
 	defer C.free(unsafe.Pointer(csource))
 
diff --git a/lxc_test.go b/lxc_test.go
index 0dca793..3e8d3b5 100644
--- a/lxc_test.go
+++ b/lxc_test.go
@@ -690,6 +690,17 @@ func TestInterfaces(t *testing.T) {
 	}
 }
 
+func TestInterfaceStats(t *testing.T) {
+	c, err := NewContainer(ContainerName)
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
+	if _, err := c.InterfaceStats(); err != nil {
+		t.Errorf(err.Error())
+	}
+}
+
 func TestMemoryUsage(t *testing.T) {
 	c, err := NewContainer(ContainerName)
 	if err != nil {


More information about the lxc-devel mailing list