[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