mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-07-19 08:10:38 +00:00

Adjust code for Lua 5.2 and keep compatibility with Lua 5.1. Signed-off-by: Natanael Copa <ncopa@alpinelinux.org> Acked-by: Dwight Engen <dwight.engen@oracle.com> Acked-by: Stéphane Graber <stgraber@ubuntu.com>
436 lines
11 KiB
Lua
Executable File
436 lines
11 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 cgroup_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
|
|
|
|
function dirname(path)
|
|
local f,output
|
|
f = io.popen("dirname " .. path)
|
|
output = f:read('*all')
|
|
f:close()
|
|
return output:sub(1,-2)
|
|
end
|
|
|
|
function basename(path, suffix)
|
|
local f,output
|
|
f = io.popen("basename " .. path .. " " .. (suffix or ""))
|
|
output = f:read('*all')
|
|
f:close()
|
|
return output:sub(1,-2)
|
|
end
|
|
|
|
function cgroup_path_get()
|
|
local f,line,cgroup_path
|
|
|
|
f = io.open("/proc/mounts", "r")
|
|
if (f) then
|
|
while true do
|
|
local c
|
|
line = f:read()
|
|
if line == nil then
|
|
break
|
|
end
|
|
c = line:split(" ", 6)
|
|
if (c[1] == "cgroup") then
|
|
cgroup_path = dirname(c[2])
|
|
break
|
|
end
|
|
end
|
|
f:close()
|
|
end
|
|
if (not cgroup_path) then
|
|
cgroup_path = "/sys/fs/cgroup"
|
|
end
|
|
return cgroup_path
|
|
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: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_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_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(controller, item, coords)
|
|
local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
|
|
local lines = {}
|
|
local result = {}
|
|
|
|
if (not f) then
|
|
for k,c in ipairs(coords) do
|
|
table.insert(result, 0)
|
|
end
|
|
else
|
|
for line in f:lines() do
|
|
table.insert(lines, line)
|
|
end
|
|
f:close()
|
|
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(controller, item)
|
|
local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
|
|
if (not f) then
|
|
return 0
|
|
end
|
|
|
|
local line = f:read()
|
|
f:close()
|
|
-- 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(controller, item, match, column)
|
|
local val
|
|
local f = io.open(cgroup_path.."/"..controller.."/lxc/"..self.ctname.."/"..item, "r")
|
|
if (not f) then
|
|
return 0
|
|
end
|
|
|
|
for line in f:lines() do
|
|
printf("matching line:%s with match:%s\n", line, match)
|
|
if (string.find(line, match)) then
|
|
local col
|
|
|
|
col = line:split(" ", 80)
|
|
val = tonumber(col[column]) or 0
|
|
printf("found line!! val:%d\n", val)
|
|
end
|
|
end
|
|
f:close()
|
|
return val
|
|
end
|
|
|
|
function container:stats_get(total)
|
|
local stat = {}
|
|
stat.mem_used = self:stat_get_int("memory", "memory.usage_in_bytes")
|
|
stat.mem_limit = self:stat_get_int("memory", "memory.limit_in_bytes")
|
|
stat.memsw_used = self:stat_get_int("memory", "memory.memsw.usage_in_bytes")
|
|
stat.memsw_limit = self:stat_get_int("memory", "memory.memsw.limit_in_bytes")
|
|
stat.cpu_use_nanos = self:stat_get_int("cpuacct", "cpuacct.usage")
|
|
stat.cpu_use_user,
|
|
stat.cpu_use_sys = self:stat_get_ints("cpuacct", "cpuacct.stat", {{1, 2}, {2, 2}})
|
|
stat.blkio = self:stat_match_get_int("blkio", "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.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.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 attr
|
|
|
|
-- the lxc directory won't exist if no containers has ever been started
|
|
attr = lfs.attributes(cgroup_path .. "/cpu/lxc")
|
|
if (not attr) then
|
|
return containers
|
|
end
|
|
|
|
for file in lfs.dir(cgroup_path .. "/cpu/lxc") do
|
|
if (file ~= "." and file ~= "..")
|
|
then
|
|
local pathfile = cgroup_path .. "/cpu/lxc/" .. file
|
|
local attr = lfs.attributes(pathfile)
|
|
|
|
if (attr.mode == "directory") then
|
|
if (names_only) then
|
|
-- note, this is a "mixed" table, ie both dictionary and list
|
|
containers[file] = true
|
|
table.insert(containers, file)
|
|
else
|
|
local ct = container:new(file)
|
|
-- note, this is a "mixed" table, ie both dictionary and list
|
|
containers[file] = ct
|
|
table.insert(containers, file)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(containers, function (a,b) return (a < b) end)
|
|
return containers
|
|
end
|
|
|
|
lxc_path = core.default_config_path_get()
|
|
cgroup_path = cgroup_path_get()
|
|
|
|
return M
|