1
0
mirror of https://github.com/lxc/go-lxc.git synced 2026-02-05 15:47:17 +01:00
Files
go-lxc/container.go

1330 lines
33 KiB
Go
Raw Normal View History

2014-10-03 17:04:27 -04:00
// Copyright © 2013, 2014, The Go-LXC Authors. All rights reserved.
2013-10-28 23:32:39 -04:00
// Use of this source code is governed by a LGPLv2.1
// license that can be found in the LICENSE file.
2013-10-28 23:32:39 -04:00
// +build linux
package lxc
// #include <lxc/lxccontainer.h>
// #include "lxc.h"
import "C"
import (
"fmt"
2014-01-28 00:13:56 -05:00
"io/ioutil"
"os/exec"
"reflect"
"strconv"
"strings"
2013-04-03 12:38:41 -04:00
"sync"
"time"
"unsafe"
)
// Container struct
type Container struct {
container *C.struct_lxc_container
2013-12-07 17:18:36 -05:00
mu sync.RWMutex
verbosity Verbosity
}
2013-10-11 17:16:41 -04:00
// Snapshot struct
type Snapshot struct {
Name string
CommentPath string
Timestamp string
Path string
}
func (c *Container) makeSure(flags int) error {
if flags&isDefined != 0 && !c.Defined() {
return ErrNotDefined
}
if flags&isNotDefined != 0 && c.Defined() {
return ErrAlreadyDefined
}
if flags&isRunning != 0 && !c.Running() {
return ErrNotRunning
}
if flags&isNotRunning != 0 && c.Running() {
return ErrAlreadyRunning
}
return nil
}
func (c *Container) cgroupItemAsByteSize(filename string, missing error) (ByteSize, error) {
size, err := strconv.ParseFloat(c.CgroupItem(filename)[0], 64)
if err != nil {
return -1, missing
}
return ByteSize(size), nil
}
func (c *Container) setCgroupItemWithByteSize(filename string, limit ByteSize, missing error) error {
if err := c.SetCgroupItem(filename, fmt.Sprintf("%.f", limit)); err != nil {
return missing
}
return nil
}
2014-01-26 18:21:51 -05:00
// 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)
}
2014-01-26 18:21:51 -05:00
// Defined returns true if the container is already defined.
func (c *Container) Defined() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return bool(C.go_lxc_defined(c.container))
}
2014-01-26 18:21:51 -05:00
// Running returns true if the container is already running.
func (c *Container) Running() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return bool(C.go_lxc_running(c.container))
}
2014-01-26 18:21:51 -05:00
// Controllable returns true if the caller can control the container.
func (c *Container) Controllable() bool {
c.mu.RLock()
defer c.mu.RUnlock()
2013-09-30 16:41:17 -04:00
return bool(C.go_lxc_may_control(c.container))
2013-09-30 16:41:17 -04:00
}
2014-01-26 18:21:51 -05:00
// CreateSnapshot creates a new snapshot.
func (c *Container) CreateSnapshot() (*Snapshot, error) {
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return nil, err
2013-10-11 17:16:41 -04:00
}
c.mu.Lock()
defer c.mu.Unlock()
2013-10-11 17:16:41 -04:00
ret := int(C.go_lxc_snapshot(c.container))
if ret < 0 {
return nil, ErrCreateSnapshotFailed
2013-10-11 17:16:41 -04:00
}
return &Snapshot{Name: fmt.Sprintf("snap%d", ret)}, nil
2013-10-11 17:16:41 -04:00
}
2014-01-26 18:21:51 -05:00
// RestoreSnapshot creates a new container based on a snapshot.
func (c *Container) RestoreSnapshot(snapshot Snapshot, name string) error {
if err := c.makeSure(isDefined); err != nil {
return err
2013-10-11 17:16:41 -04:00
}
c.mu.Lock()
defer c.mu.Unlock()
2013-10-11 17:16:41 -04:00
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csnapname := C.CString(snapshot.Name)
defer C.free(unsafe.Pointer(csnapname))
if !bool(C.go_lxc_snapshot_restore(c.container, csnapname, cname)) {
return ErrRestoreSnapshotFailed
2013-10-11 17:16:41 -04:00
}
return nil
}
2014-01-26 18:21:51 -05:00
// DestroySnapshot destroys the specified snapshot.
func (c *Container) DestroySnapshot(snapshot Snapshot) error {
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))
if !bool(C.go_lxc_snapshot_destroy(c.container, csnapname)) {
return ErrDestroySnapshotFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Snapshots returns the list of container snapshots.
func (c *Container) Snapshots() ([]Snapshot, error) {
if err := c.makeSure(isDefined); err != nil {
return nil, err
2013-10-11 17:16:41 -04:00
}
c.mu.Lock()
defer c.mu.Unlock()
var csnapshots *C.struct_lxc_snapshot
2013-10-11 17:16:41 -04:00
size := int(C.go_lxc_snapshot_list(c.container, &csnapshots))
defer freeSnapshots(csnapshots, size)
2013-10-11 17:16:41 -04:00
if size < 1 {
return nil, ErrNoSnapshot
2013-10-11 17:16:41 -04:00
}
hdr := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(csnapshots)),
Len: size,
Cap: size,
}
gosnapshots := *(*[]C.struct_lxc_snapshot)(unsafe.Pointer(&hdr))
snapshots := make([]Snapshot, size, size)
2013-10-11 17:16:41 -04:00
for i := 0; i < size; i++ {
snapshots[i] = Snapshot{
Name: C.GoString(gosnapshots[i].name),
Timestamp: C.GoString(gosnapshots[i].timestamp),
CommentPath: C.GoString(gosnapshots[i].comment_pathname),
Path: C.GoString(gosnapshots[i].lxcpath),
}
2013-10-11 17:16:41 -04:00
}
2013-10-11 17:16:41 -04:00
return snapshots, nil
}
2014-01-26 18:21:51 -05:00
// 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))]
}
// InitPid returns the process ID of the container's init process
2014-01-26 18:21:51 -05:00
// seen from outside the container.
func (c *Container) InitPid() int {
c.mu.RLock()
defer c.mu.RUnlock()
return int(C.go_lxc_init_pid(c.container))
}
2014-01-26 18:21:51 -05:00
// Daemonize returns true if the container wished to be daemonized.
func (c *Container) Daemonize() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return bool(c.container.daemonize)
}
2014-01-26 18:21:51 -05:00
// WantDaemonize determines if the container wants to run daemonized.
func (c *Container) WantDaemonize(state bool) error {
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_want_daemonize(c.container, C.bool(state))) {
return ErrDaemonizeFailed
2013-09-24 15:47:23 -04:00
}
return nil
}
2014-01-26 18:21:51 -05:00
// WantCloseAllFds determines whether container wishes all file descriptors
// to be closed on startup.
func (c *Container) WantCloseAllFds(state bool) error {
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_want_close_all_fds(c.container, C.bool(state))) {
return ErrCloseAllFdsFailed
}
return nil
}
// SetVerbosity sets the verbosity level of some API calls
func (c *Container) SetVerbosity(verbosity Verbosity) {
c.mu.Lock()
defer c.mu.Unlock()
c.verbosity = verbosity
}
2014-01-26 18:21:51 -05:00
// Freeze freezes the running container.
func (c *Container) Freeze() error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
if c.State() == FROZEN {
return ErrAlreadyFrozen
}
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_freeze(c.container)) {
return ErrFreezeFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Unfreeze thaws the frozen container.
func (c *Container) Unfreeze() error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
if c.State() != FROZEN {
return ErrNotFrozen
}
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_unfreeze(c.container)) {
return ErrUnfreezeFailed
}
return nil
}
// Create creates the container using given TemplateOptions
func (c *Container) Create(options *TemplateOptions) error {
// FIXME: Support bdevtype and bdev_specs
// bdevtypes:
// "btrfs", "zfs", "lvm", "dir"
//
// best tries to find the best backing store type
//
// bdev_specs:
// zfs requires zfsroot
// lvm requires lvname/vgname/thinpool as well as fstype and fssize
// btrfs requires nothing
// dir requires nothing
if err := c.makeSure(isNotDefined); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
2013-04-03 12:38:41 -04:00
// required parameter
if options.Template == "" {
return ErrInsufficientNumberOfArguments
}
var args []string
if options.Template == "download" {
// required parameters
if options.Distro == "" || options.Release == "" || options.Arch == "" {
return ErrInsufficientNumberOfArguments
}
args = append(args, "--dist", options.Distro, "--release", options.Release, "--arch", options.Arch)
// optional arguments
if options.Variant != "" {
args = append(args, "--variant", options.Variant)
}
if options.Server != "" {
args = append(args, "--server", options.Server)
}
if options.KeyID != "" {
args = append(args, "--keyid", options.KeyID)
}
if options.KeyServer != "" {
args = append(args, "--keyserver", options.KeyServer)
}
if options.DisableGPGValidation {
args = append(args, "--no-validate")
}
if options.FlushCache {
args = append(args, "--flush-cache")
}
if options.ForceCache {
args = append(args, "--force-cache")
}
} else {
// optional arguments
if options.Release != "" {
args = append(args, "--release", options.Release)
}
if options.Arch != "" {
args = append(args, "--arch", options.Arch)
}
if options.FlushCache {
args = append(args, "--flush-cache")
}
}
if options.ExtraArgs != nil {
args = append(args, options.ExtraArgs...)
}
ctemplate := C.CString(options.Template)
defer C.free(unsafe.Pointer(ctemplate))
cbackend := C.CString(options.Backend.String())
defer C.free(unsafe.Pointer(cbackend))
ret := false
if len(args) != 0 {
cargs := makeNullTerminatedArgs(args)
if cargs == nil {
return ErrAllocationFailed
}
defer freeNullTerminatedArgs(cargs, len(args))
ret = bool(C.go_lxc_create(c.container, ctemplate, cbackend, C.int(c.verbosity), cargs))
} else {
ret = bool(C.go_lxc_create(c.container, ctemplate, cbackend, C.int(c.verbosity), nil))
}
if !ret {
return ErrCreateFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Start starts the container.
func (c *Container) Start() error {
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
2013-04-03 12:38:41 -04:00
if !bool(C.go_lxc_start(c.container, 0, nil)) {
return ErrStartFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Execute executes the given command in a temporary container.
func (c *Container) Execute(args ...string) ([]byte, error) {
if err := c.makeSure(isNotDefined); err != nil {
return nil, err
}
cargs := []string{"lxc-execute", "-n", c.Name(), "-P", c.ConfigPath(), "--"}
cargs = append(cargs, args...)
c.mu.Lock()
defer c.mu.Unlock()
/*
2014-01-26 15:55:41 -05:00
* FIXME: Go runtime and src/lxc/start.c signal_handler are not playing nice together so use lxc-execute for now
2014-02-02 16:53:55 -05:00
* go-nuts thread: https://groups.google.com/forum/#!msg/golang-nuts/h9GbvfYv83w/5Ly_jvOr86wJ
*/
output, err := exec.Command(cargs[0], cargs[1:]...).CombinedOutput()
if err != nil {
return nil, ErrExecuteFailed
}
return output, nil
/*
cargs := makeNullTerminatedArgs(args)
if cargs == nil {
return ErrAllocationFailed
}
defer freeNullTerminatedArgs(cargs, len(args))
if !bool(C.go_lxc_start(c.container, 1, cargs)) {
return ErrExecuteFailed
}
return nil
2013-11-18 15:49:26 -05:00
*/
}
2014-01-26 18:21:51 -05:00
// Stop stops the container.
func (c *Container) Stop() error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_stop(c.container)) {
return ErrStopFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Reboot reboots the container.
func (c *Container) Reboot() error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_reboot(c.container)) {
return ErrRebootFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// Shutdown shuts down the container.
func (c *Container) Shutdown(timeout time.Duration) error {
if err := c.makeSure(isDefined | 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
}
return nil
}
2014-01-26 18:21:51 -05:00
// Destroy destroys the container.
func (c *Container) Destroy() error {
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
}
return nil
}
2014-01-26 18:21:51 -05:00
// CloneUsing clones the container using given arguments with specified backend.
//
// Additional flags to change the cloning behaviour:
// CloneKeepName, CloneKeepMACAddr, CloneSnapshot and CloneMaybeSnapshot
func (c *Container) CloneUsing(name string, backend BackendStore, flags CloneFlags) error {
// FIXME: support lxcpath, bdevtype, bdevdata, newsize and hookargs
//
// bdevtypes:
// "btrfs", "zfs", "lvm", "dir" "overlayfs"
//
// bdevdata:
// zfs requires zfsroot
// lvm requires lvname/vgname/thinpool as well as fstype and fssize
// btrfs requires nothing
// dir requires nothing
//
// newsize: for blockdev-backed backingstores
//
// hookargs: additional arguments to pass to the clone hook script
if err := c.makeSure(isDefined | isNotRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
2013-04-24 19:32:25 -04:00
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cbackend := C.CString(backend.String())
defer C.free(unsafe.Pointer(cbackend))
if !bool(C.go_lxc_clone(c.container, cname, C.int(flags), cbackend)) {
return ErrCloneFailed
2013-04-24 19:32:25 -04:00
}
return nil
}
2014-01-26 18:21:51 -05:00
// Clone clones the container using the Directory backendstore.
func (c *Container) Clone(name string) error {
return c.CloneUsing(name, Directory, 0)
2013-04-25 14:05:11 -04:00
}
2014-01-26 18:21:51 -05:00
// Rename renames the container.
func (c *Container) Rename(name string) error {
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))
if !bool(C.go_lxc_rename(c.container, cname)) {
return ErrRenameFailed
}
return nil
}
// Wait waits for container to reach a particular state.
func (c *Container) Wait(state State, timeout time.Duration) bool {
c.mu.Lock()
defer c.mu.Unlock()
cstate := C.CString(state.String())
defer C.free(unsafe.Pointer(cstate))
return bool(C.go_lxc_wait(c.container, cstate, C.int(timeout.Seconds())))
}
2014-01-26 18:21:51 -05:00
// ConfigFileName returns the container's configuration file's name.
func (c *Container) ConfigFileName() string {
c.mu.RLock()
defer c.mu.RUnlock()
2014-01-26 15:55:41 -05:00
// allocated in lxc.c
configFileName := C.go_lxc_config_file_name(c.container)
defer C.free(unsafe.Pointer(configFileName))
return C.GoString(configFileName)
}
2014-01-26 18:21:51 -05:00
// ConfigItem returns the value of the given config item.
func (c *Container) ConfigItem(key string) []string {
c.mu.RLock()
defer c.mu.RUnlock()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
2014-01-26 15:55:41 -05:00
// allocated in lxc.c
configItem := C.go_lxc_get_config_item(c.container, ckey)
defer C.free(unsafe.Pointer(configItem))
ret := strings.TrimSpace(C.GoString(configItem))
return strings.Split(ret, "\n")
}
2014-01-26 18:21:51 -05:00
// 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()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
cvalue := C.CString(value)
defer C.free(unsafe.Pointer(cvalue))
if !bool(C.go_lxc_set_config_item(c.container, ckey, cvalue)) {
return ErrSettingConfigItemFailed
}
return nil
}
2014-01-27 23:41:27 -05:00
// RunningConfigItem returns the value of the given config item.
func (c *Container) RunningConfigItem(key string) []string {
c.mu.RLock()
defer c.mu.RUnlock()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
// allocated in lxc.c
configItem := C.go_lxc_get_running_config_item(c.container, ckey)
2014-01-27 23:41:27 -05:00
defer C.free(unsafe.Pointer(configItem))
ret := strings.TrimSpace(C.GoString(configItem))
return strings.Split(ret, "\n")
}
2014-01-26 18:21:51 -05:00
// CgroupItem returns the value of the given cgroup subsystem value.
func (c *Container) CgroupItem(key string) []string {
c.mu.RLock()
defer c.mu.RUnlock()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
2014-01-26 15:55:41 -05:00
// allocated in lxc.c
cgroupItem := C.go_lxc_get_cgroup_item(c.container, ckey)
defer C.free(unsafe.Pointer(cgroupItem))
ret := strings.TrimSpace(C.GoString(cgroupItem))
return strings.Split(ret, "\n")
}
2014-01-26 18:21:51 -05:00
// 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()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
cvalue := C.CString(value)
defer C.free(unsafe.Pointer(cvalue))
if !bool(C.go_lxc_set_cgroup_item(c.container, ckey, cvalue)) {
return ErrSettingCgroupItemFailed
}
return nil
}
// ClearConfig completely clears the containers in-memory configuration.
func (c *Container) ClearConfig() {
c.mu.Lock()
defer c.mu.Unlock()
C.go_lxc_clear_config(c.container)
}
2014-01-26 18:21:51 -05:00
// ClearConfigItem clears the value of given config item.
func (c *Container) ClearConfigItem(key string) error {
c.mu.Lock()
defer c.mu.Unlock()
ckey := C.CString(key)
defer C.free(unsafe.Pointer(ckey))
if !bool(C.go_lxc_clear_config_item(c.container, ckey)) {
return ErrClearingCgroupItemFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// ConfigKeys returns the names of the config items.
func (c *Container) ConfigKeys(key ...string) []string {
c.mu.RLock()
defer c.mu.RUnlock()
var keys *_Ctype_char
if key != nil && len(key) == 1 {
ckey := C.CString(key[0])
defer C.free(unsafe.Pointer(ckey))
2014-01-26 15:55:41 -05:00
// allocated in lxc.c
keys = C.go_lxc_get_keys(c.container, ckey)
defer C.free(unsafe.Pointer(keys))
} else {
2014-01-26 15:55:41 -05:00
// allocated in lxc.c
keys = C.go_lxc_get_keys(c.container, nil)
defer C.free(unsafe.Pointer(keys))
}
ret := strings.TrimSpace(C.GoString(keys))
return strings.Split(ret, "\n")
}
2014-01-26 18:21:51 -05:00
// LoadConfigFile loads the configuration file from given path.
func (c *Container) LoadConfigFile(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
if !bool(C.go_lxc_load_config(c.container, cpath)) {
return ErrLoadConfigFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// SaveConfigFile saves the configuration file to given path.
func (c *Container) SaveConfigFile(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
if !bool(C.go_lxc_save_config(c.container, cpath)) {
return ErrSaveConfigFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// 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))
}
2014-01-26 18:21:51 -05:00
// SetConfigPath sets the configuration file's path.
func (c *Container) SetConfigPath(path string) error {
c.mu.Lock()
defer c.mu.Unlock()
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
if !bool(C.go_lxc_set_config_path(c.container, cpath)) {
return ErrSettingConfigPathFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// MemoryUsage returns memory usage of the container in bytes.
func (c *Container) MemoryUsage() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.usage_in_bytes", ErrMemLimit)
}
// MemoryLimit returns memory limit of the container in bytes.
func (c *Container) MemoryLimit() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.limit_in_bytes", ErrMemLimit)
}
// SetMemoryLimit sets memory limit of the container in bytes.
func (c *Container) SetMemoryLimit(limit ByteSize) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
return c.setCgroupItemWithByteSize("memory.limit_in_bytes", limit, ErrSettingMemoryLimitFailed)
}
// SoftMemoryLimit returns soft memory limit of the container in bytes.
func (c *Container) SoftMemoryLimit() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.soft_limit_in_bytes", ErrSoftMemLimit)
}
// SetSoftMemoryLimit sets soft memory limit of the container in bytes.
func (c *Container) SetSoftMemoryLimit(limit ByteSize) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
return c.setCgroupItemWithByteSize("memory.soft_limit_in_bytes", limit, ErrSettingSoftMemoryLimitFailed)
}
// KernelMemoryUsage returns current kernel memory allocation of the container in bytes.
func (c *Container) KernelMemoryUsage() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.kmem.usage_in_bytes", ErrKMemLimit)
}
// KernelMemoryLimit returns kernel memory limit of the container in bytes.
func (c *Container) KernelMemoryLimit() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.kmem.usage_in_bytes", ErrKMemLimit)
}
// SetKernelMemoryLimit sets kernel memory limit of the container in bytes.
func (c *Container) SetKernelMemoryLimit(limit ByteSize) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
return c.setCgroupItemWithByteSize("memory.kmem.limit_in_bytes", limit, ErrSettingKMemoryLimitFailed)
}
// MemorySwapUsage returns memory+swap usage of the container in bytes.
func (c *Container) MemorySwapUsage() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.memsw.usage_in_bytes", ErrMemorySwapLimit)
}
// MemorySwapLimit returns the memory+swap limit of the container in bytes.
func (c *Container) MemorySwapLimit() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
return c.cgroupItemAsByteSize("memory.memsw.limit_in_bytes", ErrMemorySwapLimit)
2013-04-03 12:38:41 -04:00
}
// SetMemorySwapLimit sets memory+swap limit of the container in bytes.
func (c *Container) SetMemorySwapLimit(limit ByteSize) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
return c.setCgroupItemWithByteSize("memory.memsw.limit_in_bytes", limit, ErrSettingMemorySwapLimitFailed)
}
// BlkioUsage returns number of bytes transferred to/from the disk by the container.
func (c *Container) BlkioUsage() (ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
c.mu.RLock()
defer c.mu.RUnlock()
for _, v := range c.CgroupItem("blkio.throttle.io_service_bytes") {
b := strings.Split(v, " ")
if b[0] == "Total" {
blkioUsed, err := strconv.ParseFloat(b[1], 64)
if err != nil {
return -1, err
}
return ByteSize(blkioUsed), nil
}
}
return -1, ErrBlkioUsage
}
2014-01-26 18:21:51 -05:00
// 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) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return -1, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cpuUsage, err := strconv.ParseInt(c.CgroupItem("cpuacct.usage")[0], 10, 64)
if err != nil {
return -1, err
}
return time.Duration(cpuUsage), nil
}
2013-04-03 12:38:41 -04:00
// 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) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cpuTimes := make(map[int]time.Duration)
for i, v := range strings.Split(c.CgroupItem("cpuacct.usage_percpu")[0], " ") {
cpuUsage, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
2013-04-03 12:38:41 -04:00
}
cpuTimes[i] = time.Duration(cpuUsage)
2013-04-03 12:38:41 -04:00
}
return cpuTimes, nil
2013-04-03 12:38:41 -04:00
}
// 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.
2014-01-26 22:47:39 -05:00
func (c *Container) CPUStats() (map[string]int64, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cpuStat := c.CgroupItem("cpuacct.stat")
2014-01-26 22:47:39 -05:00
user, err := strconv.ParseInt(strings.Split(cpuStat[0], "user ")[1], 10, 64)
if err != nil {
return nil, err
}
2014-01-26 22:47:39 -05:00
system, err := strconv.ParseInt(strings.Split(cpuStat[1], "system ")[1], 10, 64)
if err != nil {
return nil, err
2013-04-03 12:38:41 -04:00
}
2014-01-26 22:47:39 -05:00
return map[string]int64{"user": user, "system": system}, nil
2013-04-03 12:38:41 -04:00
}
2014-02-18 20:11:38 -05:00
// ConsoleFd allocates a console tty from container
// ttynum: tty number to attempt to allocate or -1 to allocate the first available tty
//
// Returns "ttyfd" on success, -1 on failure. The returned "ttyfd" is
// used to keep the tty allocated. The caller should close "ttyfd" to
// indicate that it is done with the allocated console so that it can
// be allocated by another caller.
2014-02-18 20:11:38 -05:00
func (c *Container) ConsoleFd(ttynum int) (int, error) {
// FIXME: Make idiomatic
if err := c.makeSure(isDefined | 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
}
return ret, nil
}
// Console allocates and runs a console tty from container
// ttynum: tty number to attempt to allocate, -1 to allocate the first available tty, or 0 to allocate the console
// stdinfd: fd to read input from
// stdoutfd: fd to write output to
// stderrfd: fd to write error output to
// escape: the escape character (1 == 'a', 2 == 'b', ...)
//
// This function will not return until the console has been exited by the user.
2014-02-02 16:15:09 -05:00
func (c *Container) Console(ttynum int, stdinfd, stdoutfd, stderrfd uintptr, escape int) error {
// FIXME: Make idiomatic
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
if !bool(C.go_lxc_console(c.container, C.int(ttynum), C.int(stdinfd),
C.int(stdoutfd), C.int(stderrfd), C.int(escape))) {
return ErrAttachFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// AttachShell attaches a shell to the container.
// It clears all environment variables before attaching.
func (c *Container) AttachShell(options *AttachOptions) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
cenv := makeNullTerminatedArgs(options.Env)
if cenv == nil {
return ErrAllocationFailed
}
defer freeNullTerminatedArgs(cenv, len(options.Env))
cenvToKeep := makeNullTerminatedArgs(options.EnvToKeep)
if cenvToKeep == nil {
return ErrAllocationFailed
}
defer freeNullTerminatedArgs(cenvToKeep, len(options.EnvToKeep))
cwd := C.CString(options.Cwd)
defer C.free(unsafe.Pointer(cwd))
ret := int(C.go_lxc_attach(c.container,
C.bool(options.ClearEnv),
C.int(options.Namespaces),
C.long(options.Arch),
2014-10-04 15:38:49 -04:00
C.uid_t(options.UID),
C.gid_t(options.GID),
C.int(options.StdinFd),
C.int(options.StdoutFd),
C.int(options.StderrFd),
cwd,
cenv,
cenvToKeep,
))
if ret < 0 {
return ErrAttachFailed
}
return nil
}
// RunCommand attachs a shell and runs the command within the container.
// 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) {
if len(args) == 0 {
return false, ErrInsufficientNumberOfArguments
}
if err := c.makeSure(isDefined | isRunning); err != nil {
return false, err
}
c.mu.Lock()
defer c.mu.Unlock()
cargs := makeNullTerminatedArgs(args)
if cargs == nil {
return false, ErrAllocationFailed
}
defer freeNullTerminatedArgs(cargs, len(args))
cenv := makeNullTerminatedArgs(options.Env)
if cenv == nil {
return false, ErrAllocationFailed
}
defer freeNullTerminatedArgs(cenv, len(options.Env))
cenvToKeep := makeNullTerminatedArgs(options.EnvToKeep)
if cenvToKeep == nil {
return false, ErrAllocationFailed
}
defer freeNullTerminatedArgs(cenvToKeep, len(options.EnvToKeep))
cwd := C.CString(options.Cwd)
defer C.free(unsafe.Pointer(cwd))
ret := int(C.go_lxc_attach_run_wait(
c.container,
C.bool(options.ClearEnv),
C.int(options.Namespaces),
C.long(options.Arch),
2014-10-04 15:38:49 -04:00
C.uid_t(options.UID),
C.gid_t(options.GID),
C.int(options.StdinFd),
C.int(options.StdoutFd),
C.int(options.StderrFd),
cwd,
cenv,
cenvToKeep,
cargs,
))
if ret < 0 {
return false, ErrAttachFailed
}
return ret == 0, nil
}
2014-01-26 18:21:51 -05:00
// Interfaces returns the names of the network interfaces.
func (c *Container) Interfaces() ([]string, error) {
if err := c.makeSure(isDefined | 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
}
return convertArgs(result), nil
}
2014-01-28 00:13:56 -05:00
// InterfaceStats returns the stats about container's network interfaces
func (c *Container) InterfaceStats() (map[string]map[string]ByteSize, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
var interfaceName string
statistics := make(map[string]map[string]ByteSize)
for i := 0; i < len(c.ConfigItem("lxc.network")); i++ {
interfaceType := c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.type", i))
if interfaceType == nil {
continue
}
if interfaceType[0] == "veth" {
interfaceName = c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))[0]
} else {
interfaceName = c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.link", i))[0]
}
for _, v := range []string{"rx", "tx"} {
/* tx and rx are reversed from the host vs container */
content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/statistics/%s_bytes", interfaceName, v))
if err != nil {
return nil, err
}
bytes, err := strconv.ParseInt(strings.Split(string(content), "\n")[0], 10, 64)
if err != nil {
return nil, err
}
if statistics[interfaceName] == nil {
statistics[interfaceName] = make(map[string]ByteSize)
}
statistics[interfaceName][v] = ByteSize(bytes)
}
}
return statistics, nil
}
2014-01-26 18:21:51 -05:00
// IPAddress returns the IP address of the given network interface.
func (c *Container) IPAddress(interfaceName string) ([]string, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cinterface := C.CString(interfaceName)
defer C.free(unsafe.Pointer(cinterface))
result := C.go_lxc_get_ips(c.container, cinterface, nil, 0)
if result == nil {
return nil, ErrIPAddress
}
return convertArgs(result), nil
}
// WaitIPAddresses waits until IPAddresses call returns something or time outs
func (c *Container) WaitIPAddresses(timeout time.Duration) ([]string, error) {
now := time.Now()
for {
if result, err := c.IPAddresses(); err == nil && len(result) > 0 {
return result, nil
}
// Python API sleeps 1 second as well
time.Sleep(1 * time.Second)
if time.Since(now) >= timeout {
return nil, ErrIPAddresses
}
}
}
2014-01-26 18:21:51 -05:00
// IPAddresses returns all IP addresses.
func (c *Container) IPAddresses() ([]string, error) {
if err := c.makeSure(isDefined | 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
}
return convertArgs(result), nil
}
2014-01-26 18:21:51 -05:00
// IPv4Addresses returns all IPv4 addresses.
func (c *Container) IPv4Addresses() ([]string, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cfamily := C.CString("inet")
defer C.free(unsafe.Pointer(cfamily))
result := C.go_lxc_get_ips(c.container, nil, cfamily, 0)
if result == nil {
return nil, ErrIPv4Addresses
}
return convertArgs(result), nil
}
2014-01-26 18:21:51 -05:00
// IPv6Addresses returns all IPv6 addresses.
func (c *Container) IPv6Addresses() ([]string, error) {
if err := c.makeSure(isDefined | isRunning); err != nil {
return nil, err
}
c.mu.RLock()
defer c.mu.RUnlock()
cfamily := C.CString("inet6")
defer C.free(unsafe.Pointer(cfamily))
result := C.go_lxc_get_ips(c.container, nil, cfamily, 0)
if result == nil {
return nil, ErrIPv6Addresses
}
return convertArgs(result), nil
}
2013-11-04 14:31:59 -05:00
2014-01-26 18:21:51 -05:00
// LogFile returns the name of the logfile.
func (c *Container) LogFile() string {
return c.ConfigItem("lxc.logfile")[0]
2013-11-04 14:31:59 -05:00
}
2014-01-26 18:21:51 -05:00
// SetLogFile sets the name of the logfile.
func (c *Container) SetLogFile(filename string) error {
if err := c.SetConfigItem("lxc.logfile", filename); err != nil {
2013-11-04 14:31:59 -05:00
return err
}
return nil
}
2014-01-26 18:21:51 -05:00
// LogLevel returns the level of the logfile.
func (c *Container) LogLevel() LogLevel {
return logLevelMap[c.ConfigItem("lxc.loglevel")[0]]
2013-11-04 14:31:59 -05:00
}
2014-01-26 18:21:51 -05:00
// SetLogLevel sets the level of the logfile.
func (c *Container) SetLogLevel(level LogLevel) error {
if err := c.SetConfigItem("lxc.loglevel", level.String()); err != nil {
2013-11-04 14:31:59 -05:00
return err
}
return nil
}
2014-01-26 18:21:51 -05:00
// AddDeviceNode adds specified device to the container.
func (c *Container) AddDeviceNode(source string, destination ...string) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
if destination != nil && len(destination) == 1 {
cdestination := C.CString(destination[0])
defer C.free(unsafe.Pointer(cdestination))
if !bool(C.go_lxc_add_device_node(c.container, csource, cdestination)) {
return ErrAddDeviceNodeFailed
}
return nil
}
if !bool(C.go_lxc_add_device_node(c.container, csource, nil)) {
return ErrAddDeviceNodeFailed
}
return nil
}
2014-01-26 18:21:51 -05:00
// RemoveDeviceNode removes the specified device from the container.
func (c *Container) RemoveDeviceNode(source string, destination ...string) error {
if err := c.makeSure(isDefined | isRunning); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
if destination != nil && len(destination) == 1 {
cdestination := C.CString(destination[0])
defer C.free(unsafe.Pointer(cdestination))
if !bool(C.go_lxc_remove_device_node(c.container, csource, cdestination)) {
return ErrRemoveDeviceNodeFailed
}
return nil
}
if !bool(C.go_lxc_remove_device_node(c.container, csource, nil)) {
return ErrRemoveDeviceNodeFailed
}
return nil
}