]> git.proxmox.com Git - mirror_lxc.git/blob - src/lua-lxc/lxc.lua
tree-wide: cleanup
[mirror_lxc.git] / src / lua-lxc / lxc.lua
1 --
2 -- lua lxc module
3 --
4 -- Copyright © 2012 Oracle.
5 --
6 -- Authors:
7 -- Dwight Engen <dwight.engen@oracle.com>
8 --
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.
13 --
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.
18 --
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
22 --
23
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)
30
31 local lxc_path
32 local log_level = 3
33
34 -- lua 5.1 compat
35 if table.unpack == nil then
36 table.unpack = unpack
37 end
38
39 -- the following two functions can be useful for debugging
40 function printf(...)
41 local function wrapper(...) io.write(string.format(...)) end
42 local status, result = pcall(wrapper, ...)
43 if not status then
44 error(result, 2)
45 end
46 end
47
48 function log(level, ...)
49 if (log_level >= level) then
50 printf(os.date("%Y-%m-%d %T "))
51 printf(...)
52 end
53 end
54
55 function string:split(delim, max_cols)
56 local cols = {}
57 local start = 1
58 local nextc
59 repeat
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
64 else
65 table.insert(cols, string.sub(self, start, string.len(self)))
66 nextc = nil
67 end
68 until nextc == nil or start > #self
69 return cols
70 end
71
72 -- container class
73 container = {}
74 container_mt = {}
75 container_mt.__index = container
76
77 function container:new(lname, config)
78 local lcore
79 local lnetcfg = {}
80 local lstats = {}
81
82 if lname then
83 if config then
84 lcore = core.container_new(lname, config)
85 else
86 lcore = core.container_new(lname)
87 end
88 end
89
90 return setmetatable({ctname = lname, core = lcore, netcfg = lnetcfg, stats = lstats}, container_mt)
91 end
92
93 -- methods interfacing to core functionality
94 function container:attach(what, ...)
95 return self.core:attach(what, ...)
96 end
97
98 function container:config_file_name()
99 return self.core:config_file_name()
100 end
101
102 function container:defined()
103 return self.core:defined()
104 end
105
106 function container:init_pid()
107 return self.core:init_pid()
108 end
109
110 function container:name()
111 return self.core:name()
112 end
113
114 function container:start()
115 return self.core:start()
116 end
117
118 function container:stop()
119 return self.core:stop()
120 end
121
122 function container:shutdown(timeout)
123 return self.core:shutdown(timeout)
124 end
125
126 function container:wait(state, timeout)
127 return self.core:wait(state, timeout)
128 end
129
130 function container:freeze()
131 return self.core:freeze()
132 end
133
134 function container:unfreeze()
135 return self.core:unfreeze()
136 end
137
138 function container:running()
139 return self.core:running()
140 end
141
142 function container:state()
143 return self.core:state()
144 end
145
146 function container:create(template, ...)
147 return self.core:create(template, ...)
148 end
149
150 function container:destroy()
151 return self.core:destroy()
152 end
153
154 -- return nil if name missing
155 function container:rename(name)
156 return self.core:rename(name)
157 end
158
159 function container:get_config_path()
160 return self.core:get_config_path()
161 end
162
163 function container:set_config_path(path)
164 return self.core:set_config_path(path)
165 end
166
167 function container:append_config_item(key, value)
168 return self.core:set_config_item(key, value)
169 end
170
171 function container:clear_config_item(key)
172 return self.core:clear_config_item(key)
173 end
174
175 function container:get_cgroup_item(key)
176 return self.core:get_cgroup_item(key)
177 end
178
179 function container:get_config_item(key)
180 local value
181 local vals = {}
182
183 value = self.core:get_config_item(key)
184
185 -- check if it is a single item
186 if (not value or not string.find(value, "\n")) then
187 return value
188 end
189
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
194 vals[v] = true
195 end
196 return vals
197 end
198
199 function container:set_cgroup_item(key, value)
200 return self.core:set_cgroup_item(key, value)
201 end
202
203 function container:set_config_item(key, value)
204 return self.core:set_config_item(key, value)
205 end
206
207 function container:get_keys(base)
208 local ktab = {}
209 local keys
210
211 if (base) then
212 keys = self.core:get_keys(base)
213 base = base .. "."
214 else
215 keys = self.core:get_keys()
216 base = ""
217 end
218 if (keys == nil) then
219 return nil
220 end
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)
225 end
226 return ktab
227 end
228
229 -- return nil or more args
230 function container:get_interfaces()
231 return self.core:get_interfaces()
232 end
233
234 -- return nil or more args
235 function container:get_ips(...)
236 return self.core:get_ips(...)
237 end
238
239 function container:load_config(alt_path)
240 if (alt_path) then
241 return self.core:load_config(alt_path)
242 else
243 return self.core:load_config()
244 end
245 end
246
247 function container:save_config(alt_path)
248 if (alt_path) then
249 return self.core:save_config(alt_path)
250 else
251 return self.core:save_config()
252 end
253 end
254
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)
258 local lines = {}
259 local result = {}
260 local flines = self:get_cgroup_item(item)
261
262 if (flines == nil) then
263 for k,c in ipairs(coords) do
264 table.insert(result, 0)
265 end
266 else
267 for line in flines:gmatch("[^\r\n]+") do
268 table.insert(lines, line)
269 end
270 for k,c in ipairs(coords) do
271 local col
272
273 col = lines[c[1]]:split(" ", 80)
274 local val = tonumber(col[c[2]])
275 table.insert(result, val)
276 end
277 end
278 return table.unpack(result)
279 end
280
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
287 end
288
289 function container:stat_match_get_int(item, match, column)
290 local val
291 local lines = self:get_cgroup_item(item)
292
293 if (lines == nil) then
294 return 0
295 end
296
297 for line in lines:gmatch("[^\r\n]+") do
298 if (string.find(line, match)) then
299 local col
300
301 col = line:split(" ", 80)
302 val = tonumber(col[column]) or 0
303 end
304 end
305
306 return val
307 end
308
309 function container:stats_get(total)
310 local stat = {}
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")
318 stat.cpu_use_user,
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)
321
322 if (total) then
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
333 end
334 return stat
335 end
336
337 local M = { container = container }
338
339 function M.stats_clear(stat)
340 stat.mem_used = 0
341 stat.mem_limit = 0
342 stat.memsw_used = 0
343 stat.memsw_limit = 0
344 stat.kmem_used = 0
345 stat.kmem_limit = 0
346 stat.cpu_use_nanos = 0
347 stat.cpu_use_user = 0
348 stat.cpu_use_sys = 0
349 stat.blkio = 0
350 end
351
352 -- return configured containers found in LXC_PATH directory
353 function M.containers_configured(names_only)
354 local containers = {}
355
356 for dir in lfs.dir(lxc_path) do
357 if (dir ~= "." and dir ~= "..")
358 then
359 local cfgfile = lxc_path .. "/" .. dir .. "/config"
360 local cfgattr = lfs.attributes(cfgfile)
361
362 if (cfgattr and cfgattr.mode == "file") then
363 if (names_only) then
364 -- note, this is a "mixed" table, ie both dictionary and list
365 containers[dir] = true
366 table.insert(containers, dir)
367 else
368 local ct = container:new(dir)
369 -- note, this is a "mixed" table, ie both dictionary and list
370 containers[dir] = ct
371 table.insert(containers, dir)
372 end
373 end
374 end
375 end
376 table.sort(containers, function (a,b) return (a < b) end)
377 return containers
378 end
379
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)
384
385 for _,name in ipairs(names) do
386 local ct = container:new(name)
387 if ct:running() then
388 -- note, this is a "mixed" table, ie both dictionary and list
389 table.insert(containers, name)
390 if (names_only) then
391 containers[name] = true
392 ct = nil
393 else
394 containers[name] = ct
395 end
396 end
397 end
398
399 table.sort(containers, function (a,b) return (a < b) end)
400 return containers
401 end
402
403 function M.version_get()
404 return core.version_get()
405 end
406
407 function M.default_config_path_get()
408 return core.default_config_path_get()
409 end
410
411 function M.cmd_get_config_item(name, item, lxcpath)
412 if (lxcpath) then
413 return core.cmd_get_config_item(name, item, lxcpath)
414 else
415 return core.cmd_get_config_item(name, item)
416 end
417 end
418
419 lxc_path = core.default_config_path_get()
420
421 return M