mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-07-14 04:20:16 +00:00
407 lines
9.9 KiB
Lua
Executable File
407 lines
9.9 KiB
Lua
Executable File
--
|
|
-- lua lxc module
|
|
--
|
|
-- Copyright © 2012 Oracle.
|
|
--
|
|
-- Authors:
|
|
-- Dwight Engen <dwight.engen@oracle.com>
|
|
--
|
|
-- This library is free software; you can redistribute it and/or
|
|
-- modify it under the terms of the GNU Lesser General Public
|
|
-- License as published by the Free Software Foundation; either
|
|
-- version 2.1 of the License, or (at your option) any later version.
|
|
--
|
|
-- This library is distributed in the hope that it will be useful,
|
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
-- Lesser General Public License for more details.
|
|
--
|
|
-- You should have received a copy of the GNU Lesser General Public
|
|
-- License along with this library; if not, write to the Free Software
|
|
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
--
|
|
|
|
local core = require("lxc.core")
|
|
local lfs = require("lfs")
|
|
local table = require("table")
|
|
local string = require("string")
|
|
local io = require("io")
|
|
module("lxc", package.seeall)
|
|
|
|
local lxc_path
|
|
local log_level = 3
|
|
|
|
-- lua 5.1 compat
|
|
if table.unpack == nil then
|
|
table.unpack = unpack
|
|
end
|
|
|
|
-- the following two functions can be useful for debugging
|
|
function printf(...)
|
|
local function wrapper(...) io.write(string.format(...)) end
|
|
local status, result = pcall(wrapper, ...)
|
|
if not status then
|
|
error(result, 2)
|
|
end
|
|
end
|
|
|
|
function log(level, ...)
|
|
if (log_level >= level) then
|
|
printf(os.date("%Y-%m-%d %T "))
|
|
printf(...)
|
|
end
|
|
end
|
|
|
|
function string:split(delim, max_cols)
|
|
local cols = {}
|
|
local start = 1
|
|
local nextc
|
|
repeat
|
|
nextc = string.find(self, delim, start)
|
|
if (nextc and #cols ~= max_cols - 1) then
|
|
table.insert(cols, string.sub(self, start, nextc-1))
|
|
start = nextc + #delim
|
|
else
|
|
table.insert(cols, string.sub(self, start, string.len(self)))
|
|
nextc = nil
|
|
end
|
|
until nextc == nil or start > #self
|
|
return cols
|
|
end
|
|
|
|
-- container class
|
|
container = {}
|
|
container_mt = {}
|
|
container_mt.__index = container
|
|
|
|
function container:new(lname, config)
|
|
local lcore
|
|
local lnetcfg = {}
|
|
local lstats = {}
|
|
|
|
if lname then
|
|
if config then
|
|
lcore = core.container_new(lname, config)
|
|
else
|
|
lcore = core.container_new(lname)
|
|
end
|
|
end
|
|
|
|
return setmetatable({ctname = lname, core = lcore, netcfg = lnetcfg, stats = lstats}, container_mt)
|
|
end
|
|
|
|
-- methods interfacing to core functionality
|
|
function container:attach(what, ...)
|
|
return self.core:attach(what, ...)
|
|
end
|
|
|
|
function container:config_file_name()
|
|
return self.core:config_file_name()
|
|
end
|
|
|
|
function container:defined()
|
|
return self.core:defined()
|
|
end
|
|
|
|
function container:init_pid()
|
|
return self.core:init_pid()
|
|
end
|
|
|
|
function container:name()
|
|
return self.core:name()
|
|
end
|
|
|
|
function container:start()
|
|
return self.core:start()
|
|
end
|
|
|
|
function container:stop()
|
|
return self.core:stop()
|
|
end
|
|
|
|
function container:shutdown(timeout)
|
|
return self.core:shutdown(timeout)
|
|
end
|
|
|
|
function container:wait(state, timeout)
|
|
return self.core:wait(state, timeout)
|
|
end
|
|
|
|
function container:freeze()
|
|
return self.core:freeze()
|
|
end
|
|
|
|
function container:unfreeze()
|
|
return self.core:unfreeze()
|
|
end
|
|
|
|
function container:running()
|
|
return self.core:running()
|
|
end
|
|
|
|
function container:state()
|
|
return self.core:state()
|
|
end
|
|
|
|
function container:create(template, ...)
|
|
return self.core:create(template, ...)
|
|
end
|
|
|
|
function container:destroy()
|
|
return self.core:destroy()
|
|
end
|
|
|
|
function container:get_config_path()
|
|
return self.core:get_config_path()
|
|
end
|
|
|
|
function container:set_config_path(path)
|
|
return self.core:set_config_path(path)
|
|
end
|
|
|
|
function container:append_config_item(key, value)
|
|
return self.core:set_config_item(key, value)
|
|
end
|
|
|
|
function container:clear_config_item(key)
|
|
return self.core:clear_config_item(key)
|
|
end
|
|
|
|
function container:get_cgroup_item(key)
|
|
return self.core:get_cgroup_item(key)
|
|
end
|
|
|
|
function container:get_config_item(key)
|
|
local value
|
|
local vals = {}
|
|
|
|
value = self.core:get_config_item(key)
|
|
|
|
-- check if it is a single item
|
|
if (not value or not string.find(value, "\n")) then
|
|
return value
|
|
end
|
|
|
|
-- it must be a list type item, make a table of it
|
|
vals = value:split("\n", 1000)
|
|
-- make it a "mixed" table, ie both dictionary and list for ease of use
|
|
for _,v in ipairs(vals) do
|
|
vals[v] = true
|
|
end
|
|
return vals
|
|
end
|
|
|
|
function container:set_cgroup_item(key, value)
|
|
return self.core:set_cgroup_item(key, value)
|
|
end
|
|
|
|
function container:set_config_item(key, value)
|
|
return self.core:set_config_item(key, value)
|
|
end
|
|
|
|
function container:get_keys(base)
|
|
local ktab = {}
|
|
local keys
|
|
|
|
if (base) then
|
|
keys = self.core:get_keys(base)
|
|
base = base .. "."
|
|
else
|
|
keys = self.core:get_keys()
|
|
base = ""
|
|
end
|
|
if (keys == nil) then
|
|
return nil
|
|
end
|
|
keys = keys:split("\n", 1000)
|
|
for _,v in ipairs(keys) do
|
|
local config_item = base .. v
|
|
ktab[v] = self.core:get_config_item(config_item)
|
|
end
|
|
return ktab
|
|
end
|
|
|
|
function container:load_config(alt_path)
|
|
if (alt_path) then
|
|
return self.core:load_config(alt_path)
|
|
else
|
|
return self.core:load_config()
|
|
end
|
|
end
|
|
|
|
function container:save_config(alt_path)
|
|
if (alt_path) then
|
|
return self.core:save_config(alt_path)
|
|
else
|
|
return self.core:save_config()
|
|
end
|
|
end
|
|
|
|
-- methods for stats collection from various cgroup files
|
|
-- read integers at given coordinates from a cgroup file
|
|
function container:stat_get_ints(item, coords)
|
|
local lines = {}
|
|
local result = {}
|
|
local flines = self:get_cgroup_item(item)
|
|
|
|
if (flines == nil) then
|
|
for k,c in ipairs(coords) do
|
|
table.insert(result, 0)
|
|
end
|
|
else
|
|
for line in flines:gmatch("[^\r\n]+") do
|
|
table.insert(lines, line)
|
|
end
|
|
for k,c in ipairs(coords) do
|
|
local col
|
|
|
|
col = lines[c[1]]:split(" ", 80)
|
|
local val = tonumber(col[c[2]])
|
|
table.insert(result, val)
|
|
end
|
|
end
|
|
return table.unpack(result)
|
|
end
|
|
|
|
-- read an integer from a cgroup file
|
|
function container:stat_get_int(item)
|
|
local line = self:get_cgroup_item(item)
|
|
-- if line is nil (on an error like Operation not supported because
|
|
-- CONFIG_MEMCG_SWAP_ENABLED isn't enabled) return 0
|
|
return tonumber(line) or 0
|
|
end
|
|
|
|
function container:stat_match_get_int(item, match, column)
|
|
local val
|
|
local lines = self:get_cgroup_item(item)
|
|
|
|
if (lines == nil) then
|
|
return 0
|
|
end
|
|
|
|
for line in lines:gmatch("[^\r\n]+") do
|
|
if (string.find(line, match)) then
|
|
local col
|
|
|
|
col = line:split(" ", 80)
|
|
val = tonumber(col[column]) or 0
|
|
end
|
|
end
|
|
|
|
return val
|
|
end
|
|
|
|
function container:stats_get(total)
|
|
local stat = {}
|
|
stat.mem_used = self:stat_get_int("memory.usage_in_bytes")
|
|
stat.mem_limit = self:stat_get_int("memory.limit_in_bytes")
|
|
stat.memsw_used = self:stat_get_int("memory.memsw.usage_in_bytes")
|
|
stat.memsw_limit = self:stat_get_int("memory.memsw.limit_in_bytes")
|
|
stat.kmem_used = self:stat_get_int("memory.kmem.usage_in_bytes")
|
|
stat.kmem_limit = self:stat_get_int("memory.kmem.limit_in_bytes")
|
|
stat.cpu_use_nanos = self:stat_get_int("cpuacct.usage")
|
|
stat.cpu_use_user,
|
|
stat.cpu_use_sys = self:stat_get_ints("cpuacct.stat", {{1, 2}, {2, 2}})
|
|
stat.blkio = self:stat_match_get_int("blkio.throttle.io_service_bytes", "Total", 2)
|
|
|
|
if (total) then
|
|
total.mem_used = total.mem_used + stat.mem_used
|
|
total.mem_limit = total.mem_limit + stat.mem_limit
|
|
total.memsw_used = total.memsw_used + stat.memsw_used
|
|
total.memsw_limit = total.memsw_limit + stat.memsw_limit
|
|
total.kmem_used = total.kmem_used + stat.kmem_used
|
|
total.kmem_limit = total.kmem_limit + stat.kmem_limit
|
|
total.cpu_use_nanos = total.cpu_use_nanos + stat.cpu_use_nanos
|
|
total.cpu_use_user = total.cpu_use_user + stat.cpu_use_user
|
|
total.cpu_use_sys = total.cpu_use_sys + stat.cpu_use_sys
|
|
total.blkio = total.blkio + stat.blkio
|
|
end
|
|
return stat
|
|
end
|
|
|
|
local M = { container = container }
|
|
|
|
function M.stats_clear(stat)
|
|
stat.mem_used = 0
|
|
stat.mem_limit = 0
|
|
stat.memsw_used = 0
|
|
stat.memsw_limit = 0
|
|
stat.kmem_used = 0
|
|
stat.kmem_limit = 0
|
|
stat.cpu_use_nanos = 0
|
|
stat.cpu_use_user = 0
|
|
stat.cpu_use_sys = 0
|
|
stat.blkio = 0
|
|
end
|
|
|
|
-- return configured containers found in LXC_PATH directory
|
|
function M.containers_configured(names_only)
|
|
local containers = {}
|
|
|
|
for dir in lfs.dir(lxc_path) do
|
|
if (dir ~= "." and dir ~= "..")
|
|
then
|
|
local cfgfile = lxc_path .. "/" .. dir .. "/config"
|
|
local cfgattr = lfs.attributes(cfgfile)
|
|
|
|
if (cfgattr and cfgattr.mode == "file") then
|
|
if (names_only) then
|
|
-- note, this is a "mixed" table, ie both dictionary and list
|
|
containers[dir] = true
|
|
table.insert(containers, dir)
|
|
else
|
|
local ct = container:new(dir)
|
|
-- note, this is a "mixed" table, ie both dictionary and list
|
|
containers[dir] = ct
|
|
table.insert(containers, dir)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(containers, function (a,b) return (a < b) end)
|
|
return containers
|
|
end
|
|
|
|
-- return running containers found in cgroup fs
|
|
function M.containers_running(names_only)
|
|
local containers = {}
|
|
local names = M.containers_configured(true)
|
|
|
|
for _,name in ipairs(names) do
|
|
local ct = container:new(name)
|
|
if ct:running() then
|
|
-- note, this is a "mixed" table, ie both dictionary and list
|
|
table.insert(containers, name)
|
|
if (names_only) then
|
|
containers[name] = true
|
|
ct = nil
|
|
else
|
|
containers[name] = ct
|
|
end
|
|
end
|
|
end
|
|
|
|
table.sort(containers, function (a,b) return (a < b) end)
|
|
return containers
|
|
end
|
|
|
|
function M.version_get()
|
|
return core.version_get()
|
|
end
|
|
|
|
function M.default_config_path_get()
|
|
return core.default_config_path_get()
|
|
end
|
|
|
|
function M.cmd_get_config_item(name, item, lxcpath)
|
|
if (lxcpath) then
|
|
return core.cmd_get_config_item(name, item, lxcpath)
|
|
else
|
|
return core.cmd_get_config_item(name, item)
|
|
end
|
|
end
|
|
|
|
lxc_path = core.default_config_path_get()
|
|
|
|
return M
|