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.limits_changed = False
62 self.config_file = "/etc/vservers/%s.conf" % name
63 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
64 if not (os.path.isdir(self.dir) and
65 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
66 raise NoSuchVServer, "no such vserver: " + name
68 for config_file in ["/etc/vservers.conf", self.config_file]:
70 self.config.update(self.__read_config_file(config_file))
72 if ex.errno != errno.ENOENT:
74 self.remove_caps = ~vserverimpl.CAP_SAFE;
76 vm_id = int(self.config['S_CONTEXT'])
78 self.vm_running = vm_running
80 # For every resource key listed in the limit table, add in a
81 # new method with which one can get/set the resource's hard,
82 # soft, minimum limits.
83 limits = {"CPU": RLIMIT_CPU,
85 "NPROC": RLIMIT_NPROC,
86 "NOFILE": RLIMIT_NOFILE,
87 "MEMLOCK": RLIMIT_MEMLOCK,
89 "LOCKS": RLIMIT_LOCKS,
90 "SIGPENDING": RLIMIT_SIGPENDING,
91 "MSGQUEUE": RLIMIT_MSGQUEUE,
92 "NSOCK": VLIMIT_NSOCK,
93 "OPENFD": VLIMIT_OPENFD,
95 "SHMEM": VLIMIT_SHMEM}
96 for meth in limits.keys():
97 resource_type = limits[meth]
101 minimum=VC_LIM_KEEP:\
102 self.__set_vserver_limit(resource_type,\
106 self.__dict__["set_%s_limit"%meth] = func
111 minimum=VC_LIM_KEEP:\
112 self.__set_vserver_config(meth, resource_type, \
116 self.__dict__["set_%s_config"%meth] = func
118 func = lambda : self.__get_vserver_limit(resource_type)
119 self.__dict__["get_%s_limit"%meth] = func
121 func = lambda : self.__get_vserver_config(meth,resource_type)
122 self.__dict__["get_%s_config"%meth] = func
124 def have_limits_changed(self):
125 return self.limits_changed
127 def __set_vserver_limit(self,resource_type,hard,soft,minimum):
128 """Generic set resource limit function for vserver"""
129 if self.is_running():
131 old_hard, old_soft, old_minimum = self.__get_vserver_limit(resource_type)
132 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
133 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
134 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
135 self.limits_changed = self.limits_changed or changed
136 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
138 def __set_vserver_config(self,meth,resource_type,hard,soft,minimum):
139 """Generic set resource limit function for vserver"""
141 if hard <> VC_LIM_KEEP:
142 resources["VS_%s_HARD"%meth] = hard
143 if soft <> VC_LIM_KEEP:
144 resources["VS_%s_SOFT"%meth] = soft
145 if minimum <> VC_LIM_KEEP:
146 resources["VS_%s_MINIMUM"%meth] = minimum
148 self.update_resources(resources)
149 self.__set_vserver_limit(resource_type,hard,soft,minimum)
151 def __get_vserver_limit(self,resource_type):
152 """Generic get resource configuration function for vserver"""
153 if self.is_running():
154 ret = vserverimpl.getrlimit(self.ctx,resource_type)
156 ret = __get_vserver_config(meth,resource_type)
159 def __get_vserver_config(self,meth,resource_type):
160 """Generic get resource configuration function for vserver"""
161 hard = int(self.config.get("VS_%s_HARD"%meth,VC_LIM_KEEP))
162 soft = int(self.config.get("VS_%s_SOFT"%meth,VC_LIM_KEEP))
163 minimum = int(self.config.get("VS_%s_MINIMUM"%meth,VC_LIM_KEEP))
164 return (hard,soft,minimum)
166 def set_WHITELISTED_config(self,whitelisted):
167 resources = {'VS_WHITELISTED': whitelisted}
168 self.update_resources(resources)
170 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
172 def __read_config_file(self, filename):
174 f = open(filename, "r")
178 for m in self.config_var_re.finditer(data):
179 (key, val) = m.groups()
180 config[key] = val.strip('"')
183 def __update_config_file(self, filename, newvars):
185 # read old file, apply changes
186 f = open(filename, "r")
189 todo = newvars.copy()
192 for m in self.config_var_re.finditer(data):
193 (key, val) = m.groups()
194 newval = todo.pop(key, None)
196 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
197 offset += len(str(newval)) - (m.end(2)-m.start(2))
199 for (newkey, newval) in todo.items():
200 data += "%s=%s\n" % (newkey, newval)
207 newfile = filename + ".new"
208 f = open(newfile, "w")
212 # replace old file with new
213 os.rename(newfile, filename)
215 def __do_chroot(self):
220 def chroot_call(self, fn, *args):
222 cwd_fd = os.open(".", os.O_RDONLY)
224 root_fd = os.open("/", os.O_RDONLY)
237 def set_disklimit(self, block_limit):
238 # block_limit is in kB
240 vserverimpl.unsetdlimit(self.dir, self.ctx)
244 block_usage = vserverimpl.DLIMIT_KEEP
245 inode_usage = vserverimpl.DLIMIT_KEEP
247 # init_disk_info() must have been called to get usage values
248 block_usage = self.disk_blocks
249 inode_usage = self.disk_inodes
251 vserverimpl.setdlimit(self.dir,
256 vserverimpl.DLIMIT_INF, # inode limit
257 2) # %age reserved for root
259 resources = {'VS_DISK_MAX': block_limit}
260 self.update_resources(resources)
262 def is_running(self):
263 return vserverimpl.isrunning(self.ctx)
265 def get_disklimit(self):
268 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
269 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
271 if ex.errno != errno.ESRCH:
273 # get here if no vserver disk limit has been set for xid
278 def set_sched_config(self, cpu_share, sched_flags):
280 """ Write current CPU scheduler parameters to the vserver
281 configuration file. This method does not modify the kernel CPU
282 scheduling parameters for this context. """
284 if cpu_share == int(self.config.get("CPULIMIT", -1)):
286 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
287 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
288 self.update_resources(cpu_config)
290 self.set_sched(cpu_share, sched_flags)
292 def set_sched(self, cpu_share, sched_flags = 0):
293 """ Update kernel CPU scheduling parameters for this context. """
294 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
297 # have no way of querying scheduler right now on a per vserver basis
300 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
301 exempt_min = None, exempt_max = None,
302 share = None, dev = "eth0"):
305 bwlimit.off(self.ctx, dev)
307 bwlimit.on(self.ctx, dev, share,
308 minrate, maxrate, exempt_min, exempt_max)
310 def get_bwlimit(self, dev = "eth0"):
312 result = bwlimit.get(self.ctx)
313 # result of bwlimit.get is (ctx, share, minrate, maxrate)
318 def open(self, filename, mode = "r", bufsize = -1):
320 return self.chroot_call(open, filename, mode, bufsize)
322 def __do_chcontext(self, state_file):
325 print >>state_file, "S_CONTEXT=%u" % self.ctx
326 print >>state_file, "S_PROFILE="
329 if vserverimpl.chcontext(self.ctx):
331 vserverimpl.setup_done(self.ctx)
333 def __prep(self, runlevel, log):
335 """ Perform all the crap that the vserver script does before
336 actually executing the startup scripts. """
338 # remove /var/run and /var/lock/subsys files
339 # but don't remove utmp from the top-level /var/run
341 LOCKDIR = "/var/lock/subsys"
342 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
343 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
344 (out + map((dir + "/").__add__, ff(files)),
346 list(os.walk(RUNDIR)),
348 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
349 os.listdir(LOCKDIR)))
353 # set the initial runlevel
354 f = open(RUNDIR + "/utmp", "w")
355 utmp.set_runlevel(f, runlevel)
358 # mount /proc and /dev/pts
359 self.__do_mount("none", "/proc", "proc")
360 # XXX - magic mount options
361 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
363 def __do_mount(self, *mount_args):
366 mountimpl.mount(*mount_args)
368 if ex.errno == errno.EBUSY:
369 # assume already mounted
375 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
377 self.__do_chcontext(state_file)
379 def start(self, wait, runlevel = 3):
380 self.vm_running = True
381 self.limits_changed = False
383 child_pid = os.fork()
390 # open state file to record vserver info
391 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
393 # use /dev/null for stdin, /var/log/boot.log for stdout/err
396 os.open("/dev/null", os.O_RDONLY)
398 log = open("/var/log/boot.log", "w", 0)
401 print >>log, ("%s: starting the virtual server %s" %
402 (time.asctime(time.gmtime()), self.name))
404 # perform pre-init cleanup
405 self.__prep(runlevel, log)
407 # execute each init script in turn
408 # XXX - we don't support all scripts that vserver script does
411 for cmd in self.INITSCRIPTS + [None]:
412 # wait for previous command to terminate, unless it
413 # is the last one and the caller has specified to wait
414 if cmd_pid and (cmd != None or wait):
416 os.waitpid(cmd_pid, 0)
418 print >>log, "error waiting for %s:" % cmd_pid
419 traceback.print_exc()
425 # fork and exec next command
429 # enter vserver context
430 self.__do_chcontext(state_file)
431 arg_subst = { 'runlevel': runlevel }
432 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
434 print >>log, "executing '%s'" % " ".join(cmd_args)
435 os.execl(cmd[0], *cmd_args)
437 traceback.print_exc()
440 # don't want to write state_file multiple times
443 # we get here due to an exception in the top-level child process
444 except Exception, ex:
445 traceback.print_exc()
451 def set_resources(self):
453 """ Called when vserver context is entered for first time,
454 should be overridden by subclass. """
458 def update_resources(self, resources):
460 self.config.update(resources)
462 # write new values to configuration file
463 self.__update_config_file(self.config_file, resources)
465 def init_disk_info(self):
467 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
471 def stop(self, signal = signal.SIGKILL):
472 vserverimpl.killall(self.ctx, signal)
473 self.vm_running = False
474 self.limits_changed = False
478 def create(vm_name, static = False, ctor = VServer):
482 options += ['--static']
483 runcmd.run('vuseradd', options + [vm_name])
484 vm_id = pwd.getpwnam(vm_name)[2]
486 return ctor(vm_name, vm_id)