1 # Copyright 2005 Princeton University
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 'S_CONTEXT': 'context',
69 'VS_WHITELISTED': 'whitelisted',
70 'CPULIMIT': 'sched/fill-rate2',
71 'CPUGUARANTEED': 'sched/fill-rate',
72 'VS_DISK_MAX': 'dlimits/0/space_total',
75 def __init__(self, name, directory):
77 for i in RLIMITS.keys():
78 for j in ('HARD', 'SOFT', 'MINIMUM'):
79 self.mapping['VS_%s_%s' % (i, j)] = 'rlimits/%s.%s' % (i.lower(), j.replace('MINIMUM', 'min').lower())
83 def get(self, option, default = None):
85 filename = self.mapping[option]
86 f = open(os.path.join(self.dir, filename), "r")
87 buf = f.readline().rstrip()
91 # No mapping exists for this option
94 if default is not None:
97 raise KeyError, "Key %s is not set for %s" % (option, self.name)
99 def update(self, vars):
100 for (option, value) in vars.iteritems():
102 old_umask = os.umask(0022)
103 filename = os.path.join(self.dir, self.mapping[option])
105 os.makedirs(os.path.dirname(filename), 0755)
108 f = open(filename, 'w')
109 f.write("%s\n" % value)
113 raise KeyError, "Don't know how to handle %s, sorry" % option
118 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
119 ('/etc/rc.d/rc', '%(runlevel)d')]
121 def __init__(self, name, vm_id = None, vm_running = False):
124 self.rlimits_changed = False
125 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
126 if not (os.path.isdir(self.dir) and
127 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
128 raise NoSuchVServer, "no such vserver: " + name
129 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
130 self.remove_caps = ~vserverimpl.CAP_SAFE;
132 vm_id = int(self.config.get('S_CONTEXT'))
134 self.vm_running = vm_running
136 def have_limits_changed(self):
137 return self.rlimits_changed
139 def set_rlimit_limit(self,type,hard,soft,minimum):
140 """Generic set resource limit function for vserver"""
144 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
145 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
146 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
147 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
148 self.rlimits_changed = self.rlimits_changed or changed
150 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
152 resource_type = RLIMITS[type]
154 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
156 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
158 def set_rlimit_config(self,type,hard,soft,minimum):
159 """Generic set resource limit function for vserver"""
161 if hard <> VC_LIM_KEEP:
162 resources["VS_%s_HARD"%type] = hard
163 if soft <> VC_LIM_KEEP:
164 resources["VS_%s_SOFT"%type] = soft
165 if minimum <> VC_LIM_KEEP:
166 resources["VS_%s_MINIMUM"%type] = minimum
168 self.update_resources(resources)
169 self.set_rlimit_limit(type,hard,soft,minimum)
171 def get_rlimit_limit(self,type):
172 """Generic get resource configuration function for vserver"""
174 resource_type = RLIMITS[type]
176 ret = vserverimpl.getrlimit(self.ctx,resource_type)
178 print "Unexpected error with getrlimit for context %d" % self.ctx
179 ret = self.get_rlimit_config(type)
182 def get_rlimit_config(self,type):
183 """Generic get resource configuration function for vserver"""
184 hard = int(self.config.get("VS_%s_HARD"%type,VC_LIM_KEEP))
185 soft = int(self.config.get("VS_%s_SOFT"%type,VC_LIM_KEEP))
186 minimum = int(self.config.get("VS_%s_MINIMUM"%type,VC_LIM_KEEP))
187 return (hard,soft,minimum)
189 def set_WHITELISTED_config(self,whitelisted):
190 resources = {'VS_WHITELISTED': whitelisted}
191 self.update_resources(resources)
193 def __do_chroot(self):
198 def chroot_call(self, fn, *args):
200 cwd_fd = os.open(".", os.O_RDONLY)
202 root_fd = os.open("/", os.O_RDONLY)
215 def set_disklimit(self, block_limit):
216 # block_limit is in kB
219 vserverimpl.unsetdlimit(self.dir, self.ctx)
221 print "Unexpected error with unsetdlimit for context %d" % self.ctx
225 block_usage = vserverimpl.DLIMIT_KEEP
226 inode_usage = vserverimpl.DLIMIT_KEEP
228 # init_disk_info() must have been called to get usage values
229 block_usage = self.disk_blocks
230 inode_usage = self.disk_inodes
234 vserverimpl.setdlimit(self.dir,
239 vserverimpl.DLIMIT_INF, # inode limit
240 2) # %age reserved for root
242 print "Unexpected error with setdlimit for context %d" % self.ctx
245 resources = {'VS_DISK_MAX': block_limit}
246 self.update_resources(resources)
248 def is_running(self):
249 return vserverimpl.isrunning(self.ctx)
251 def get_disklimit(self):
254 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
255 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
257 if ex.errno != errno.ESRCH:
259 # get here if no vserver disk limit has been set for xid
264 def set_sched_config(self, cpu_share, sched_flags):
266 """ Write current CPU scheduler parameters to the vserver
267 configuration file. This method does not modify the kernel CPU
268 scheduling parameters for this context. """
270 if cpu_share == int(self.config.get("CPULIMIT", -1)):
272 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
273 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
274 self.update_resources(cpu_config)
276 self.set_sched(cpu_share, sched_flags)
278 def set_sched(self, cpu_share, sched_flags = 0):
279 """ Update kernel CPU scheduling parameters for this context. """
280 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
283 # have no way of querying scheduler right now on a per vserver basis
286 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
287 exempt_min = None, exempt_max = None,
288 share = None, dev = "eth0"):
291 bwlimit.off(self.ctx, dev)
293 bwlimit.on(self.ctx, dev, share,
294 minrate, maxrate, exempt_min, exempt_max)
296 def get_bwlimit(self, dev = "eth0"):
298 result = bwlimit.get(self.ctx)
299 # result of bwlimit.get is (ctx, share, minrate, maxrate)
304 def open(self, filename, mode = "r", bufsize = -1):
306 return self.chroot_call(open, filename, mode, bufsize)
308 def __do_chcontext(self, state_file):
311 print >>state_file, "%u" % self.ctx
314 if vserverimpl.chcontext(self.ctx):
316 vserverimpl.setup_done(self.ctx)
318 def __prep(self, runlevel, log):
320 """ Perform all the crap that the vserver script does before
321 actually executing the startup scripts. """
323 # remove /var/run and /var/lock/subsys files
324 # but don't remove utmp from the top-level /var/run
326 LOCKDIR = "/var/lock/subsys"
327 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
328 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
329 (out + map((dir + "/").__add__, ff(files)),
331 list(os.walk(RUNDIR)),
333 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
334 os.listdir(LOCKDIR)))
338 # set the initial runlevel
339 f = open(RUNDIR + "/utmp", "w")
340 utmp.set_runlevel(f, runlevel)
343 # mount /proc and /dev/pts
344 self.__do_mount("none", "/proc", "proc")
345 # XXX - magic mount options
346 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
348 def __do_mount(self, *mount_args):
351 mountimpl.mount(*mount_args)
353 if ex.errno == errno.EBUSY:
354 # assume already mounted
360 self.__do_chcontext(None)
362 def start(self, wait, runlevel = 3):
363 self.vm_running = True
364 self.rlimits_changed = False
366 child_pid = os.fork()
373 # open state file to record vserver info
374 state_file = open("/var/run/vservers/%s" % self.name, "w")
376 # use /dev/null for stdin, /var/log/boot.log for stdout/err
379 os.open("/dev/null", os.O_RDONLY)
381 log = open("/var/log/boot.log", "w", 0)
384 print >>log, ("%s: starting the virtual server %s" %
385 (time.asctime(time.gmtime()), self.name))
387 # perform pre-init cleanup
388 self.__prep(runlevel, log)
390 # execute each init script in turn
391 # XXX - we don't support all scripts that vserver script does
392 self.__do_chcontext(state_file)
393 for cmd in self.INITSCRIPTS + [None]:
395 # enter vserver context
396 arg_subst = { 'runlevel': runlevel }
397 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
399 print >>log, "executing '%s'" % " ".join(cmd_args)
400 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
402 traceback.print_exc()
405 # we get here due to an exception in the top-level child process
406 except Exception, ex:
407 traceback.print_exc()
413 def set_resources(self):
415 """ Called when vserver context is entered for first time,
416 should be overridden by subclass. """
420 def update_resources(self, resources):
422 self.config.update(resources)
424 def init_disk_info(self):
425 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
426 (child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
428 line = child_stdout.readline()
430 sys.stderr.write(child_stderr.readline())
431 (space, inodes) = line.split()
432 self.disk_inodes = int(inodes)
433 self.disk_blocks = int(space)
434 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
436 return self.disk_blocks * 1024
438 def stop(self, signal = signal.SIGKILL):
439 vserverimpl.killall(self.ctx, signal)
440 self.vm_running = False
441 self.rlimits_changed = False
445 def create(vm_name, static = False, ctor = VServer):
449 options += ['--static']
450 runcmd.run('vuseradd', options + [vm_name])
451 vm_id = pwd.getpwnam(vm_name)[2]
453 return ctor(vm_name, vm_id)