1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.59 2007/07/17 17:56:04 faiyaza Exp $
19 import cpulimit, bwlimit
21 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
22 from vserverimpl import DLIMIT_INF
23 from vserverimpl import VC_LIM_KEEP
25 from vserverimpl import RLIMIT_CPU
26 from vserverimpl import RLIMIT_RSS
27 from vserverimpl import RLIMIT_NPROC
28 from vserverimpl import RLIMIT_NOFILE
29 from vserverimpl import RLIMIT_MEMLOCK
30 from vserverimpl import RLIMIT_AS
31 from vserverimpl import RLIMIT_LOCKS
32 from vserverimpl import RLIMIT_SIGPENDING
33 from vserverimpl import RLIMIT_MSGQUEUE
34 from vserverimpl import VLIMIT_NSOCK
35 from vserverimpl import VLIMIT_OPENFD
36 from vserverimpl import VLIMIT_ANON
37 from vserverimpl import VLIMIT_SHMEM
40 # these are the flags taken from the kernel linux/vserver/legacy.h
43 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
51 RLIMITS = {"CPU": RLIMIT_CPU,
53 "NPROC": RLIMIT_NPROC,
54 "NOFILE": RLIMIT_NOFILE,
55 "MEMLOCK": RLIMIT_MEMLOCK,
57 "LOCKS": RLIMIT_LOCKS,
58 "SIGPENDING": RLIMIT_SIGPENDING,
59 "MSGQUEUE": RLIMIT_MSGQUEUE,
60 "NSOCK": VLIMIT_NSOCK,
61 "OPENFD": VLIMIT_OPENFD,
63 "SHMEM": VLIMIT_SHMEM}
65 class NoSuchVServer(Exception): pass
69 def __init__(self, name, directory):
73 def get(self, option, default = None):
75 f = open(os.path.join(self.dir, option), "r")
76 buf = f.readline().rstrip()
80 if default is not None:
83 raise KeyError, "Key %s is not set for %s" % (option, self.name)
85 def update(self, option, value):
87 old_umask = os.umask(0022)
88 filename = os.path.join(self.dir, option)
90 os.makedirs(os.path.dirname(filename), 0755)
93 f = open(filename, 'w')
94 if isinstance(value, list):
95 f.write("%s\n" % "\n".join(value))
97 f.write("%s\n" % value)
101 raise KeyError, "Don't know how to handle %s, sorry" % option
106 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
107 ('/etc/rc.d/rc', '%(runlevel)d')]
109 def __init__(self, name, vm_id = None, vm_running = None):
112 self.rlimits_changed = False
113 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
114 if not (os.path.isdir(self.dir) and
115 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
116 raise NoSuchVServer, "no such vserver: " + name
117 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
118 self.remove_caps = ~vserverimpl.CAP_SAFE;
120 vm_id = int(self.config.get('context'))
122 if vm_running == None:
123 vm_running = self.is_running()
124 self.vm_running = vm_running
126 def have_limits_changed(self):
127 return self.rlimits_changed
129 def set_rlimit_limit(self,type,hard,soft,minimum):
130 """Generic set resource limit function for vserver"""
134 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
135 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
136 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
137 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
138 self.rlimits_changed = self.rlimits_changed or changed
140 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
142 resource_type = RLIMITS[type]
144 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
146 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
148 def set_rlimit_config(self,type,hard,soft,minimum):
149 """Generic set resource limit function for vserver"""
150 if hard <> VC_LIM_KEEP:
151 self.config.update('rlimits/%s.hard' % type.lower(), hard)
152 if soft <> VC_LIM_KEEP:
153 self.config.update('rlimits/%s.soft' % type.lower(), soft)
154 if minimum <> VC_LIM_KEEP:
155 self.config.update('rlimits/%s.min' % type.lower(), minimum)
156 self.set_rlimit_limit(type,hard,soft,minimum)
158 def get_rlimit_limit(self,type):
159 """Generic get resource configuration function for vserver"""
161 resource_type = RLIMITS[type]
163 ret = vserverimpl.getrlimit(self.ctx,resource_type)
165 print "Unexpected error with getrlimit for context %d" % self.ctx
166 ret = self.get_rlimit_config(type)
169 def get_rlimit_config(self,type):
170 """Generic get resource configuration function for vserver"""
171 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
172 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
173 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
174 return (hard,soft,minimum)
176 def set_WHITELISTED_config(self,whitelisted):
177 self.config.update('whitelisted', whitelisted)
179 def set_capabilities(self, capabilities):
180 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
182 def set_capabilities_config(self, capabilities):
183 self.config.update('bcapabilities', capabilities)
184 self.set_capabilities(capabilities)
186 def get_capabilities(self):
187 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
189 def get_capabilities_config(self):
190 return self.config.get('bcapabilities', '')
192 def __do_chroot(self):
197 def chroot_call(self, fn, *args):
199 cwd_fd = os.open(".", os.O_RDONLY)
201 root_fd = os.open("/", os.O_RDONLY)
214 def set_disklimit(self, block_limit):
215 # block_limit is in kB
218 vserverimpl.unsetdlimit(self.dir, self.ctx)
220 print "Unexpected error with unsetdlimit for context %d" % self.ctx
224 block_usage = vserverimpl.DLIMIT_KEEP
225 inode_usage = vserverimpl.DLIMIT_KEEP
227 # init_disk_info() must have been called to get usage values
228 block_usage = self.disk_blocks
229 inode_usage = self.disk_inodes
233 vserverimpl.setdlimit(self.dir,
238 vserverimpl.DLIMIT_INF, # inode limit
239 2) # %age reserved for root
241 print "Unexpected error with setdlimit for context %d" % self.ctx
244 self.config.update('dlimits/0/space_total', block_limit)
246 def is_running(self):
247 return vserverimpl.isrunning(self.ctx)
249 def get_disklimit(self):
252 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
253 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
255 if ex.errno != errno.ESRCH:
257 # get here if no vserver disk limit has been set for xid
262 def set_sched_config(self, cpu_share, sched_flags):
264 """ Write current CPU scheduler parameters to the vserver
265 configuration file. This method does not modify the kernel CPU
266 scheduling parameters for this context. """
268 if sched_flags & SCHED_CPU_GUARANTEED:
269 cpu_guaranteed = cpu_share
272 self.config.update('sched/fill-rate2', cpu_share)
273 self.config.update('sched/fill-rate', cpu_guaranteed)
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, vserverimpl.text2bcaps(self.get_capabilities_config())):
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 init_disk_info(self):
421 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
422 (child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
424 line = child_stdout.readline()
426 sys.stderr.write(child_stderr.readline())
429 (space, inodes) = line.split()
430 self.disk_inodes = int(inodes)
431 self.disk_blocks = int(space)
432 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
434 return self.disk_blocks * 1024
436 def stop(self, signal = signal.SIGKILL):
437 vserverimpl.killall(self.ctx, signal)
438 self.vm_running = False
439 self.rlimits_changed = False
443 def create(vm_name, static = False, ctor = VServer):
447 options += ['--static']
448 runcmd.run('vuseradd', options + [vm_name])
449 vm_id = pwd.getpwnam(vm_name)[2]
451 return ctor(vm_name, vm_id)