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
49 RLIMITS = {"CPU": RLIMIT_CPU,
51 "NPROC": RLIMIT_NPROC,
52 "NOFILE": RLIMIT_NOFILE,
53 "MEMLOCK": RLIMIT_MEMLOCK,
55 "LOCKS": RLIMIT_LOCKS,
56 "SIGPENDING": RLIMIT_SIGPENDING,
57 "MSGQUEUE": RLIMIT_MSGQUEUE,
58 "NSOCK": VLIMIT_NSOCK,
59 "OPENFD": VLIMIT_OPENFD,
61 "SHMEM": VLIMIT_SHMEM}
63 class NoSuchVServer(Exception): pass
68 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
69 ('/etc/rc.d/rc', '%(runlevel)d')]
71 def __init__(self, name, vm_id = None, vm_running = False):
74 self.rlimits_changed = False
75 self.config_file = "/etc/vservers/%s.conf" % name
76 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
77 if not (os.path.isdir(self.dir) and
78 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
79 raise NoSuchVServer, "no such vserver: " + name
81 for config_file in ["/etc/vservers.conf", self.config_file]:
83 self.config.update(self.__read_config_file(config_file))
85 if ex.errno != errno.ENOENT:
87 self.remove_caps = ~vserverimpl.CAP_SAFE;
89 vm_id = int(self.config['S_CONTEXT'])
91 self.vm_running = vm_running
93 def have_limits_changed(self):
94 return self.rlimits_changed
96 def set_rlimit_limit(self,type,hard,soft,minimum):
97 """Generic set resource limit function for vserver"""
101 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
102 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
103 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
104 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
105 self.rlimits_changed = self.rlimits_changed or changed
107 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
109 resource_type = RLIMITS[type]
111 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
113 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
115 def set_rlimit_config(self,type,hard,soft,minimum):
116 """Generic set resource limit function for vserver"""
118 if hard <> VC_LIM_KEEP:
119 resources["VS_%s_HARD"%type] = hard
120 if soft <> VC_LIM_KEEP:
121 resources["VS_%s_SOFT"%type] = soft
122 if minimum <> VC_LIM_KEEP:
123 resources["VS_%s_MINIMUM"%type] = minimum
125 self.update_resources(resources)
126 self.set_rlimit_limit(type,hard,soft,minimum)
128 def get_rlimit_limit(self,type):
129 """Generic get resource configuration function for vserver"""
131 resource_type = RLIMITS[type]
133 ret = vserverimpl.getrlimit(self.ctx,resource_type)
135 print "Unexpected error with getrlimit for context %d" % self.ctx
136 ret = self.get_rlimit_config(type)
139 def get_rlimit_config(self,type):
140 """Generic get resource configuration function for vserver"""
141 hard = int(self.config.get("VS_%s_HARD"%type,VC_LIM_KEEP))
142 soft = int(self.config.get("VS_%s_SOFT"%type,VC_LIM_KEEP))
143 minimum = int(self.config.get("VS_%s_MINIMUM"%type,VC_LIM_KEEP))
144 return (hard,soft,minimum)
146 def set_WHITELISTED_config(self,whitelisted):
147 resources = {'VS_WHITELISTED': whitelisted}
148 self.update_resources(resources)
150 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
152 def __read_config_file(self, filename):
154 f = open(filename, "r")
158 for m in self.config_var_re.finditer(data):
159 (key, val) = m.groups()
160 config[key] = val.strip('"')
163 def __update_config_file(self, filename, newvars):
165 # read old file, apply changes
166 f = open(filename, "r")
169 todo = newvars.copy()
172 for m in self.config_var_re.finditer(data):
173 (key, val) = m.groups()
174 newval = todo.pop(key, None)
176 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
177 offset += len(str(newval)) - (m.end(2)-m.start(2))
179 for (newkey, newval) in todo.items():
180 data += "%s=%s\n" % (newkey, newval)
187 newfile = filename + ".new"
188 f = open(newfile, "w")
192 # replace old file with new
193 os.rename(newfile, filename)
195 def __do_chroot(self):
200 def chroot_call(self, fn, *args):
202 cwd_fd = os.open(".", os.O_RDONLY)
204 root_fd = os.open("/", os.O_RDONLY)
217 def set_disklimit(self, block_limit):
218 # block_limit is in kB
221 vserverimpl.unsetdlimit(self.dir, self.ctx)
223 print "Unexpected error with unsetdlimit for context %d" % self.ctx
227 block_usage = vserverimpl.DLIMIT_KEEP
228 inode_usage = vserverimpl.DLIMIT_KEEP
230 # init_disk_info() must have been called to get usage values
231 block_usage = self.disk_blocks
232 inode_usage = self.disk_inodes
236 vserverimpl.setdlimit(self.dir,
241 vserverimpl.DLIMIT_INF, # inode limit
242 2) # %age reserved for root
244 print "Unexpected error with setdlimit for context %d" % self.ctx
247 resources = {'VS_DISK_MAX': block_limit}
248 self.update_resources(resources)
250 def is_running(self):
251 return 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):
368 self.vm_running = True
369 self.rlimits_changed = False
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):
460 vserverimpl.killall(self.ctx, signal)
461 self.vm_running = False
462 self.rlimits_changed = 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)