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
67 def __init__(self, name, directory):
71 def get(self, option, default = None):
73 f = open(os.path.join(self.dir, option), "r")
74 buf = f.readline().rstrip()
78 # No mapping exists for this option
81 if default is not None:
84 raise KeyError, "Key %s is not set for %s" % (option, self.name)
86 def update(self, option, value):
88 old_umask = os.umask(0022)
89 filename = os.path.join(self.dir, option)
91 os.makedirs(os.path.dirname(filename), 0755)
94 f = open(filename, 'w')
95 if isinstance(value, list):
96 f.write("%s\n" % "\n".join(value))
98 f.write("%s\n" % value)
102 raise KeyError, "Don't know how to handle %s, sorry" % option
107 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
108 ('/etc/rc.d/rc', '%(runlevel)d')]
110 def __init__(self, name, vm_id = None, vm_running = None):
113 self.rlimits_changed = False
114 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
115 if not (os.path.isdir(self.dir) and
116 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
117 raise NoSuchVServer, "no such vserver: " + name
118 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
119 self.remove_caps = ~vserverimpl.CAP_SAFE;
121 vm_id = int(self.config.get('context'))
123 if vm_running == None:
124 vm_running = self.is_running()
125 self.vm_running = vm_running
127 def have_limits_changed(self):
128 return self.rlimits_changed
130 def set_rlimit_limit(self,type,hard,soft,minimum):
131 """Generic set resource limit function for vserver"""
135 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
136 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
137 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
138 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
139 self.rlimits_changed = self.rlimits_changed or changed
141 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
143 resource_type = RLIMITS[type]
145 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
147 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
149 def set_rlimit_config(self,type,hard,soft,minimum):
150 """Generic set resource limit function for vserver"""
151 if hard <> VC_LIM_KEEP:
152 self.config.update('rlimits/%s.hard' % type.lower(), hard)
153 if soft <> VC_LIM_KEEP:
154 self.config.update('rlimits/%s.soft' % type.lower(), soft)
155 if minimum <> VC_LIM_KEEP:
156 self.config.update('rlimits/%s.min' % type.lower(), minimum)
157 self.set_rlimit_limit(type,hard,soft,minimum)
159 def get_rlimit_limit(self,type):
160 """Generic get resource configuration function for vserver"""
162 resource_type = RLIMITS[type]
164 ret = vserverimpl.getrlimit(self.ctx,resource_type)
166 print "Unexpected error with getrlimit for context %d" % self.ctx
167 ret = self.get_rlimit_config(type)
170 def get_rlimit_config(self,type):
171 """Generic get resource configuration function for vserver"""
172 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
173 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
174 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
175 return (hard,soft,minimum)
177 def set_WHITELISTED_config(self,whitelisted):
178 self.config.update('whitelisted', whitelisted)
180 def __do_chroot(self):
185 def chroot_call(self, fn, *args):
187 cwd_fd = os.open(".", os.O_RDONLY)
189 root_fd = os.open("/", os.O_RDONLY)
202 def set_disklimit(self, block_limit):
203 # block_limit is in kB
206 vserverimpl.unsetdlimit(self.dir, self.ctx)
208 print "Unexpected error with unsetdlimit for context %d" % self.ctx
212 block_usage = vserverimpl.DLIMIT_KEEP
213 inode_usage = vserverimpl.DLIMIT_KEEP
215 # init_disk_info() must have been called to get usage values
216 block_usage = self.disk_blocks
217 inode_usage = self.disk_inodes
221 vserverimpl.setdlimit(self.dir,
226 vserverimpl.DLIMIT_INF, # inode limit
227 2) # %age reserved for root
229 print "Unexpected error with setdlimit for context %d" % self.ctx
232 self.config.update('dlimits/0/space_total', block_limit)
234 def is_running(self):
235 return vserverimpl.isrunning(self.ctx)
237 def get_disklimit(self):
240 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
241 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
243 if ex.errno != errno.ESRCH:
245 # get here if no vserver disk limit has been set for xid
250 def set_sched_config(self, cpu_share, sched_flags):
252 """ Write current CPU scheduler parameters to the vserver
253 configuration file. This method does not modify the kernel CPU
254 scheduling parameters for this context. """
256 if sched_flags & SCHED_CPU_GUARANTEED:
257 cpu_guaranteed = cpu_share
260 self.config.update('sched/fill-rate2', cpu_share)
261 self.config.update('sched/fill-rate', cpu_guaranteed)
264 self.set_sched(cpu_share, sched_flags)
266 def set_sched(self, cpu_share, sched_flags = 0):
267 """ Update kernel CPU scheduling parameters for this context. """
268 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
271 # have no way of querying scheduler right now on a per vserver basis
274 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
275 exempt_min = None, exempt_max = None,
276 share = None, dev = "eth0"):
279 bwlimit.off(self.ctx, dev)
281 bwlimit.on(self.ctx, dev, share,
282 minrate, maxrate, exempt_min, exempt_max)
284 def get_bwlimit(self, dev = "eth0"):
286 result = bwlimit.get(self.ctx)
287 # result of bwlimit.get is (ctx, share, minrate, maxrate)
292 def open(self, filename, mode = "r", bufsize = -1):
294 return self.chroot_call(open, filename, mode, bufsize)
296 def __do_chcontext(self, state_file):
299 print >>state_file, "%u" % self.ctx
302 if vserverimpl.chcontext(self.ctx):
304 vserverimpl.setup_done(self.ctx)
306 def __prep(self, runlevel, log):
308 """ Perform all the crap that the vserver script does before
309 actually executing the startup scripts. """
311 # remove /var/run and /var/lock/subsys files
312 # but don't remove utmp from the top-level /var/run
314 LOCKDIR = "/var/lock/subsys"
315 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
316 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
317 (out + map((dir + "/").__add__, ff(files)),
319 list(os.walk(RUNDIR)),
321 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
322 os.listdir(LOCKDIR)))
326 # set the initial runlevel
327 f = open(RUNDIR + "/utmp", "w")
328 utmp.set_runlevel(f, runlevel)
331 # mount /proc and /dev/pts
332 self.__do_mount("none", "/proc", "proc")
333 # XXX - magic mount options
334 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
336 def __do_mount(self, *mount_args):
339 mountimpl.mount(*mount_args)
341 if ex.errno == errno.EBUSY:
342 # assume already mounted
348 self.__do_chcontext(None)
350 def start(self, wait, runlevel = 3):
351 self.vm_running = True
352 self.rlimits_changed = False
354 child_pid = os.fork()
361 # open state file to record vserver info
362 state_file = open("/var/run/vservers/%s" % self.name, "w")
364 # use /dev/null for stdin, /var/log/boot.log for stdout/err
367 os.open("/dev/null", os.O_RDONLY)
369 log = open("/var/log/boot.log", "w", 0)
372 print >>log, ("%s: starting the virtual server %s" %
373 (time.asctime(time.gmtime()), self.name))
375 # perform pre-init cleanup
376 self.__prep(runlevel, log)
378 # execute each init script in turn
379 # XXX - we don't support all scripts that vserver script does
380 self.__do_chcontext(state_file)
381 for cmd in self.INITSCRIPTS + [None]:
383 # enter vserver context
384 arg_subst = { 'runlevel': runlevel }
385 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
387 print >>log, "executing '%s'" % " ".join(cmd_args)
388 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
390 traceback.print_exc()
393 # we get here due to an exception in the top-level child process
394 except Exception, ex:
395 traceback.print_exc()
401 def set_resources(self):
403 """ Called when vserver context is entered for first time,
404 should be overridden by subclass. """
408 def init_disk_info(self):
409 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
410 (child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
412 line = child_stdout.readline()
414 sys.stderr.write(child_stderr.readline())
415 (space, inodes) = line.split()
416 self.disk_inodes = int(inodes)
417 self.disk_blocks = int(space)
418 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
420 return self.disk_blocks * 1024
422 def stop(self, signal = signal.SIGKILL):
423 vserverimpl.killall(self.ctx, signal)
424 self.vm_running = False
425 self.rlimits_changed = False
429 def create(vm_name, static = False, ctor = VServer):
433 options += ['--static']
434 runcmd.run('vuseradd', options + [vm_name])
435 vm_id = pwd.getpwnam(vm_name)[2]
437 return ctor(vm_name, vm_id)