1 # Copyright 2005 Princeton University
16 import vserverimpl, vduimpl
17 import cpulimit, bwlimit
19 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
20 from vserverimpl import DLIMIT_INF
21 from vserverimpl import VC_LIM_KEEP
23 from vserverimpl import RLIMIT_CPU
24 from vserverimpl import RLIMIT_RSS
25 from vserverimpl import RLIMIT_NPROC
26 from vserverimpl import RLIMIT_NOFILE
27 from vserverimpl import RLIMIT_MEMLOCK
28 from vserverimpl import RLIMIT_AS
29 from vserverimpl import RLIMIT_LOCKS
30 from vserverimpl import RLIMIT_SIGPENDING
31 from vserverimpl import RLIMIT_MSGQUEUE
32 from vserverimpl import VLIMIT_NSOCK
33 from vserverimpl import VLIMIT_OPENFD
34 from vserverimpl import VLIMIT_ANON
35 from vserverimpl import VLIMIT_SHMEM
38 # these are the flags taken from the kernel linux/vserver/legacy.h
41 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
50 class NoSuchVServer(Exception): pass
55 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
56 ('/etc/rc.d/rc', '%(runlevel)d')]
58 def __init__(self, name, vm_id = None, vm_running = False):
61 self.config_file = "/etc/vservers/%s.conf" % name
62 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
63 if not (os.path.isdir(self.dir) and
64 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
65 raise NoSuchVServer, "no such vserver: " + name
67 for config_file in ["/etc/vservers.conf", self.config_file]:
69 self.config.update(self.__read_config_file(config_file))
71 if ex.errno != errno.ENOENT:
73 self.remove_caps = ~vserverimpl.CAP_SAFE;
75 vm_id = int(self.config['S_CONTEXT'])
77 self.vm_running = vm_running
79 # For every resource key listed in the limit table, add in a
80 # new method with which one can get/set the resource's hard,
81 # soft, minimum limits.
82 limits = {"CPU": RLIMIT_CPU,
84 "NPROC": RLIMIT_NPROC,
85 "NOFILE": RLIMIT_NOFILE,
86 "MEMLOCK": RLIMIT_MEMLOCK,
88 "LOCKS": RLIMIT_LOCKS,
89 "SIGPENDING": RLIMIT_SIGPENDING,
90 "MSGQUEUE": RLIMIT_MSGQUEUE,
91 "NSOCK": VLIMIT_NSOCK,
92 "OPENFD": VLIMIT_OPENFD,
94 "SHMEM": VLIMIT_SHMEM}
95 for meth in limits.keys():
96 resource_type = limits[meth]
100 minimum=VC_LIM_KEEP:\
101 self.__set_vserver_limit(resource_type,\
105 self.__dict__["set_%s_limit"%meth] = func
110 minimum=VC_LIM_KEEP:\
111 self.__set_vserver_config(meth, resource_type, \
115 self.__dict__["set_%s_config"%meth] = func
117 func = lambda : self.__get_vserver_limit(resource_type)
118 self.__dict__["get_%s_limit"%meth] = func
120 func = lambda : self.__get_vserver_config(meth,resource_type)
121 self.__dict__["get_%s_config"%meth] = func
123 def __set_vserver_limit(self,resource_type,hard,soft,minimum):
124 """Generic set resource limit function for vserver"""
125 if self.is_running():
126 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
128 def __set_vserver_config(self,meth,resource_type,hard,soft,minimum):
129 """Generic set resource limit function for vserver"""
131 if hard <> VC_LIM_KEEP:
132 resources["VS_%s_HARD"%meth] = hard
133 if soft <> VC_LIM_KEEP:
134 resources["VS_%s_SOFT"%meth] = soft
135 if minimum <> VC_LIM_KEEP:
136 resources["VS_%s_MINIMUM"%meth] = minimum
138 self.update_resources(resources)
139 self.__set_vserver_limit(resource_type,hard,soft,minimum)
141 def __get_vserver_limit(self,resource_type):
142 """Generic get resource configuration function for vserver"""
143 if self.is_running():
144 ret = vserverimpl.getrlimit(self.ctx,resource_type)
146 ret = __get_vserver_config(meth,resource_type)
149 def __get_vserver_config(self,meth,resource_type):
150 """Generic get resource configuration function for vserver"""
151 hard = int(self.config.get("VS_%s_HARD"%meth,VC_LIM_KEEP))
152 soft = int(self.config.get("VS_%s_SOFT"%meth,VC_LIM_KEEP))
153 minimum = int(self.config.get("VS_%s_MINIMUM"%meth,VC_LIM_KEEP))
154 return (hard,soft,minimum)
156 def set_WHITELISTED_config(self,whitelisted):
157 resources = {'VS_WHITELISTED': whitelisted}
158 self.update_resources(resources)
160 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
162 def __read_config_file(self, filename):
164 f = open(filename, "r")
168 for m in self.config_var_re.finditer(data):
169 (key, val) = m.groups()
170 config[key] = val.strip('"')
173 def __update_config_file(self, filename, newvars):
175 # read old file, apply changes
176 f = open(filename, "r")
179 todo = newvars.copy()
182 for m in self.config_var_re.finditer(data):
183 (key, val) = m.groups()
184 newval = todo.pop(key, None)
186 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
187 offset += len(str(newval)) - (m.end(2)-m.start(2))
189 for (newkey, newval) in todo.items():
190 data += "%s=%s\n" % (newkey, newval)
197 newfile = filename + ".new"
198 f = open(newfile, "w")
202 # replace old file with new
203 os.rename(newfile, filename)
205 def __do_chroot(self):
210 def chroot_call(self, fn, *args):
212 cwd_fd = os.open(".", os.O_RDONLY)
214 root_fd = os.open("/", os.O_RDONLY)
227 def set_disklimit(self, block_limit):
229 # block_limit is in kB
231 vserverimpl.unsetdlimit(self.dir, self.ctx)
235 block_usage = vserverimpl.DLIMIT_KEEP
236 inode_usage = vserverimpl.DLIMIT_KEEP
238 # init_disk_info() must have been called to get usage values
239 block_usage = self.disk_blocks
240 inode_usage = self.disk_inodes
242 vserverimpl.setdlimit(self.dir,
247 vserverimpl.DLIMIT_INF, # inode limit
248 2) # %age reserved for root
250 def is_running(self):
251 return self.vm_running and vserverimpl.isrunning(self.ctx)
253 def get_disklimit(self):
256 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
257 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
259 if ex.errno != errno.ESRCH:
261 # get here if no vserver disk limit has been set for xid
266 def set_sched_config(self, cpu_share, sched_flags):
268 """ Write current CPU scheduler parameters to the vserver
269 configuration file. This method does not modify the kernel CPU
270 scheduling parameters for this context. """
272 if cpu_share == int(self.config.get("CPULIMIT", -1)):
274 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
275 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
276 self.update_resources(cpu_config)
278 self.set_sched(cpu_share, sched_flags)
280 def set_sched(self, cpu_share, sched_flags = 0):
281 """ Update kernel CPU scheduling parameters for this context. """
282 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
285 # have no way of querying scheduler right now on a per vserver basis
288 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
289 exempt_min = None, exempt_max = None,
290 share = None, dev = "eth0"):
293 bwlimit.off(self.ctx, dev)
295 bwlimit.on(self.ctx, dev, share,
296 minrate, maxrate, exempt_min, exempt_max)
298 def get_bwlimit(self, dev = "eth0"):
300 result = bwlimit.get(self.ctx)
301 # result of bwlimit.get is (ctx, share, minrate, maxrate)
306 def open(self, filename, mode = "r", bufsize = -1):
308 return self.chroot_call(open, filename, mode, bufsize)
310 def __do_chcontext(self, state_file):
313 print >>state_file, "S_CONTEXT=%u" % self.ctx
314 print >>state_file, "S_PROFILE="
317 if vserverimpl.chcontext(self.ctx):
319 vserverimpl.setup_done(self.ctx)
321 def __prep(self, runlevel, log):
323 """ Perform all the crap that the vserver script does before
324 actually executing the startup scripts. """
326 # remove /var/run and /var/lock/subsys files
327 # but don't remove utmp from the top-level /var/run
329 LOCKDIR = "/var/lock/subsys"
330 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
331 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
332 (out + map((dir + "/").__add__, ff(files)),
334 list(os.walk(RUNDIR)),
336 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
337 os.listdir(LOCKDIR)))
341 # set the initial runlevel
342 f = open(RUNDIR + "/utmp", "w")
343 utmp.set_runlevel(f, runlevel)
346 # mount /proc and /dev/pts
347 self.__do_mount("none", "/proc", "proc")
348 # XXX - magic mount options
349 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
351 def __do_mount(self, *mount_args):
354 mountimpl.mount(*mount_args)
356 if ex.errno == errno.EBUSY:
357 # assume already mounted
363 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
365 self.__do_chcontext(state_file)
367 def start(self, wait, runlevel = 3):
369 self.vm_running = True
371 child_pid = os.fork()
378 # open state file to record vserver info
379 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
381 # use /dev/null for stdin, /var/log/boot.log for stdout/err
384 os.open("/dev/null", os.O_RDONLY)
386 log = open("/var/log/boot.log", "w", 0)
389 print >>log, ("%s: starting the virtual server %s" %
390 (time.asctime(time.gmtime()), self.name))
392 # perform pre-init cleanup
393 self.__prep(runlevel, log)
395 # execute each init script in turn
396 # XXX - we don't support all scripts that vserver script does
399 for cmd in self.INITSCRIPTS + [None]:
400 # wait for previous command to terminate, unless it
401 # is the last one and the caller has specified to wait
402 if cmd_pid and (cmd != None or wait):
404 os.waitpid(cmd_pid, 0)
406 print >>log, "error waiting for %s:" % cmd_pid
407 traceback.print_exc()
413 # fork and exec next command
417 # enter vserver context
418 self.__do_chcontext(state_file)
419 arg_subst = { 'runlevel': runlevel }
420 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
422 print >>log, "executing '%s'" % " ".join(cmd_args)
423 os.execl(cmd[0], *cmd_args)
425 traceback.print_exc()
428 # don't want to write state_file multiple times
431 # we get here due to an exception in the top-level child process
432 except Exception, ex:
433 traceback.print_exc()
439 def set_resources(self):
441 """ Called when vserver context is entered for first time,
442 should be overridden by subclass. """
446 def update_resources(self, resources):
448 self.config.update(resources)
450 # write new values to configuration file
451 self.__update_config_file(self.config_file, resources)
453 def init_disk_info(self):
455 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
459 def stop(self, signal = signal.SIGKILL):
461 vserverimpl.killall(self.ctx, signal)
462 self.vm_running = False
466 def create(vm_name, static = False, ctor = VServer):
470 options += ['--static']
471 runcmd.run('vuseradd', options + [vm_name])
472 vm_id = pwd.getpwnam(vm_name)[2]
474 return ctor(vm_name, vm_id)