4 -- Copyright © 2012 Oracle.
7 -- Dwight Engen <dwight.engen@oracle.com>
9 -- This library is free software; you can redistribute it and/or
10 -- modify it under the terms of the GNU Lesser General Public
11 -- License as published by the Free Software Foundation; either
12 -- version 2.1 of the License, or (at your option) any later version.
14 -- This library is distributed in the hope that it will be useful,
15 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
16 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 -- Lesser General Public License for more details.
19 -- You should have received a copy of the GNU Lesser General Public
20 -- License along with this library; if not, write to the Free Software
21 -- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 local core = require("lxc.core")
25 local lfs = require("lfs")
26 local table = require("table")
27 local string = require("string")
28 local io = require("io")
29 module("lxc", package.seeall)
35 if table.unpack == nil then
39 -- the following two functions can be useful for debugging
41 local function wrapper(...) io.write(string.format(...)) end
42 local status, result = pcall(wrapper, ...)
48 function log(level, ...)
49 if (log_level >= level) then
50 printf(os.date("%Y-%m-%d %T "))
55 function string:split(delim, max_cols)
60 nextc = string.find(self, delim, start)
61 if (nextc and #cols ~= max_cols - 1) then
62 table.insert(cols, string.sub(self, start, nextc-1))
63 start = nextc + #delim
65 table.insert(cols, string.sub(self, start, string.len(self)))
68 until nextc == nil or start > #self
75 container_mt.__index = container
77 function container:new(lname, config)
84 lcore = core.container_new(lname, config)
86 lcore = core.container_new(lname)
90 return setmetatable({ctname = lname, core = lcore, netcfg = lnetcfg, stats = lstats}, container_mt)
93 -- methods interfacing to core functionality
94 function container:attach(what, ...)
95 return self.core:attach(what, ...)
98 function container:config_file_name()
99 return self.core:config_file_name()
102 function container:defined()
103 return self.core:defined()
106 function container:init_pid()
107 return self.core:init_pid()
110 function container:name()
111 return self.core:name()
114 function container:start()
115 return self.core:start()
118 function container:stop()
119 return self.core:stop()
122 function container:shutdown(timeout)
123 return self.core:shutdown(timeout)
126 function container:wait(state, timeout)
127 return self.core:wait(state, timeout)
130 function container:freeze()
131 return self.core:freeze()
134 function container:unfreeze()
135 return self.core:unfreeze()
138 function container:running()
139 return self.core:running()
142 function container:state()
143 return self.core:state()
146 function container:create(template, ...)
147 return self.core:create(template, ...)
150 function container:destroy()
151 return self.core:destroy()
154 -- return nil if name missing
155 function container:rename(name)
156 return self.core:rename(name)
159 function container:get_config_path()
160 return self.core:get_config_path()
163 function container:set_config_path(path)
164 return self.core:set_config_path(path)
167 function container:append_config_item(key, value)
168 return self.core:set_config_item(key, value)
171 function container:clear_config_item(key)
172 return self.core:clear_config_item(key)
175 function container:get_cgroup_item(key)
176 return self.core:get_cgroup_item(key)
179 function container:get_config_item(key)
183 value = self.core:get_config_item(key)
185 -- check if it is a single item
186 if (not value or not string.find(value, "\n")) then
190 -- it must be a list type item, make a table of it
191 vals = value:split("\n", 1000)
192 -- make it a "mixed" table, ie both dictionary and list for ease of use
193 for _,v in ipairs(vals) do
199 function container:set_cgroup_item(key, value)
200 return self.core:set_cgroup_item(key, value)
203 function container:set_config_item(key, value)
204 return self.core:set_config_item(key, value)
207 function container:get_keys(base)
212 keys = self.core:get_keys(base)
215 keys = self.core:get_keys()
218 if (keys == nil) then
221 keys = keys:split("\n", 1000)
222 for _,v in ipairs(keys) do
223 local config_item = base .. v
224 ktab[v] = self.core:get_config_item(config_item)
229 -- return nil or more args
230 function container:get_interfaces()
231 return self.core:get_interfaces()
234 -- return nil or more args
235 function container:get_ips(...)
236 return self.core:get_ips(...)
239 function container:load_config(alt_path)
241 return self.core:load_config(alt_path)
243 return self.core:load_config()
247 function container:save_config(alt_path)
249 return self.core:save_config(alt_path)
251 return self.core:save_config()
255 -- methods for stats collection from various cgroup files
256 -- read integers at given coordinates from a cgroup file
257 function container:stat_get_ints(item, coords)
260 local flines = self:get_cgroup_item(item)
262 if (flines == nil) then
263 for k,c in ipairs(coords) do
264 table.insert(result, 0)
267 for line in flines:gmatch("[^\r\n]+") do
268 table.insert(lines, line)
270 for k,c in ipairs(coords) do
273 col = lines[c[1]]:split(" ", 80)
274 local val = tonumber(col[c[2]])
275 table.insert(result, val)
278 return table.unpack(result)
281 -- read an integer from a cgroup file
282 function container:stat_get_int(item)
283 local line = self:get_cgroup_item(item)
284 -- if line is nil (on an error like Operation not supported because
285 -- CONFIG_MEMCG_SWAP_ENABLED isn't enabled) return 0
286 return tonumber(line) or 0
289 function container:stat_match_get_int(item, match, column)
291 local lines = self:get_cgroup_item(item)
293 if (lines == nil) then
297 for line in lines:gmatch("[^\r\n]+") do
298 if (string.find(line, match)) then
301 col = line:split(" ", 80)
302 val = tonumber(col[column]) or 0
309 function container:stats_get(total)
311 stat.mem_used = self:stat_get_int("memory.usage_in_bytes")
312 stat.mem_limit = self:stat_get_int("memory.limit_in_bytes")
313 stat.memsw_used = self:stat_get_int("memory.memsw.usage_in_bytes")
314 stat.memsw_limit = self:stat_get_int("memory.memsw.limit_in_bytes")
315 stat.kmem_used = self:stat_get_int("memory.kmem.usage_in_bytes")
316 stat.kmem_limit = self:stat_get_int("memory.kmem.limit_in_bytes")
317 stat.cpu_use_nanos = self:stat_get_int("cpuacct.usage")
319 stat.cpu_use_sys = self:stat_get_ints("cpuacct.stat", {{1, 2}, {2, 2}})
320 stat.blkio = self:stat_match_get_int("blkio.throttle.io_service_bytes", "Total", 2)
323 total.mem_used = total.mem_used + stat.mem_used
324 total.mem_limit = total.mem_limit + stat.mem_limit
325 total.memsw_used = total.memsw_used + stat.memsw_used
326 total.memsw_limit = total.memsw_limit + stat.memsw_limit
327 total.kmem_used = total.kmem_used + stat.kmem_used
328 total.kmem_limit = total.kmem_limit + stat.kmem_limit
329 total.cpu_use_nanos = total.cpu_use_nanos + stat.cpu_use_nanos
330 total.cpu_use_user = total.cpu_use_user + stat.cpu_use_user
331 total.cpu_use_sys = total.cpu_use_sys + stat.cpu_use_sys
332 total.blkio = total.blkio + stat.blkio
337 local M = { container = container }
339 function M.stats_clear(stat)
346 stat.cpu_use_nanos = 0
347 stat.cpu_use_user = 0
352 -- return configured containers found in LXC_PATH directory
353 function M.containers_configured(names_only)
354 local containers = {}
356 for dir in lfs.dir(lxc_path) do
357 if (dir ~= "." and dir ~= "..")
359 local cfgfile = lxc_path .. "/" .. dir .. "/config"
360 local cfgattr = lfs.attributes(cfgfile)
362 if (cfgattr and cfgattr.mode == "file") then
364 -- note, this is a "mixed" table, ie both dictionary and list
365 containers[dir] = true
366 table.insert(containers, dir)
368 local ct = container:new(dir)
369 -- note, this is a "mixed" table, ie both dictionary and list
371 table.insert(containers, dir)
376 table.sort(containers, function (a,b) return (a < b) end)
380 -- return running containers found in cgroup fs
381 function M.containers_running(names_only)
382 local containers = {}
383 local names = M.containers_configured(true)
385 for _,name in ipairs(names) do
386 local ct = container:new(name)
388 -- note, this is a "mixed" table, ie both dictionary and list
389 table.insert(containers, name)
391 containers[name] = true
394 containers[name] = ct
399 table.sort(containers, function (a,b) return (a < b) end)
403 function M.version_get()
404 return core.version_get()
407 function M.default_config_path_get()
408 return core.default_config_path_get()
411 function M.cmd_get_config_item(name, item, lxcpath)
413 return core.cmd_get_config_item(name, item, lxcpath)
415 return core.cmd_get_config_item(name, item)
419 lxc_path = core.default_config_path_get()