1 # Copyright 2005 Princeton University
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 # No mapping exists for this option
83 if default is not None:
86 raise KeyError, "Key %s is not set for %s" % (option, self.name)
88 def update(self, option, value):
90 old_umask = os.umask(0022)
91 filename = os.path.join(self.dir, option)
93 os.makedirs(os.path.dirname(filename), 0755)
96 f = open(filename, 'w')
97 if isinstance(value, list):
98 f.write("%s\n" % "\n".join(value))
100 f.write("%s\n" % value)
104 raise KeyError, "Don't know how to handle %s, sorry" % option
109 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
110 ('/etc/rc.d/rc', '%(runlevel)d')]
112 def __init__(self, name, vm_id = None, vm_running = None):
115 self.rlimits_changed = False
116 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
117 if not (os.path.isdir(self.dir) and
118 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
119 raise NoSuchVServer, "no such vserver: " + name
120 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
121 self.remove_caps = ~vserverimpl.CAP_SAFE;
123 vm_id = int(self.config.get('context'))
125 if vm_running == None:
126 vm_running = self.is_running()
127 self.vm_running = vm_running
129 def have_limits_changed(self):
130 return self.rlimits_changed
132 def set_rlimit_limit(self,type,hard,soft,minimum):
133 """Generic set resource limit function for vserver"""
137 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
138 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
139 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
140 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
141 self.rlimits_changed = self.rlimits_changed or changed
143 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
145 resource_type = RLIMITS[type]
147 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
149 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
151 def set_rlimit_config(self,type,hard,soft,minimum):
152 """Generic set resource limit function for vserver"""
153 if hard <> VC_LIM_KEEP:
154 self.config.update('rlimits/%s.hard' % type.lower(), hard)
155 if soft <> VC_LIM_KEEP:
156 self.config.update('rlimits/%s.soft' % type.lower(), soft)
157 if minimum <> VC_LIM_KEEP:
158 self.config.update('rlimits/%s.min' % type.lower(), minimum)
159 self.set_rlimit_limit(type,hard,soft,minimum)
161 def get_rlimit_limit(self,type):
162 """Generic get resource configuration function for vserver"""
164 resource_type = RLIMITS[type]
166 ret = vserverimpl.getrlimit(self.ctx,resource_type)
168 print "Unexpected error with getrlimit for context %d" % self.ctx
169 ret = self.get_rlimit_config(type)
172 def get_rlimit_config(self,type):
173 """Generic get resource configuration function for vserver"""
174 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
175 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
176 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
177 return (hard,soft,minimum)
179 def set_WHITELISTED_config(self,whitelisted):
180 self.config.update('whitelisted', whitelisted)
182 def set_capabilities(self, capabilities):
183 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
185 def set_capabilities_config(self, capabilities):
186 self.config.update('bcapabilities', capabilities)
187 self.set_capabilities(capabilities)
189 def get_capabilities(self):
190 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
192 def get_capabilities_config(self):
193 return self.config.get('bcapabilities')
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 self.config.update('dlimits/0/space_total', block_limit)
249 def is_running(self):
250 return vserverimpl.isrunning(self.ctx)
252 def get_disklimit(self):
255 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
256 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
258 if ex.errno != errno.ESRCH:
260 # get here if no vserver disk limit has been set for xid
265 def set_sched_config(self, cpu_share, sched_flags):
267 """ Write current CPU scheduler parameters to the vserver
268 configuration file. This method does not modify the kernel CPU
269 scheduling parameters for this context. """
271 if sched_flags & SCHED_CPU_GUARANTEED:
272 cpu_guaranteed = cpu_share
275 self.config.update('sched/fill-rate2', cpu_share)
276 self.config.update('sched/fill-rate', cpu_guaranteed)
279 self.set_sched(cpu_share, sched_flags)
281 def set_sched(self, cpu_share, sched_flags = 0):
282 """ Update kernel CPU scheduling parameters for this context. """
283 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
286 # have no way of querying scheduler right now on a per vserver basis
289 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
290 exempt_min = None, exempt_max = None,
291 share = None, dev = "eth0"):
294 bwlimit.off(self.ctx, dev)
296 bwlimit.on(self.ctx, dev, share,
297 minrate, maxrate, exempt_min, exempt_max)
299 def get_bwlimit(self, dev = "eth0"):
301 result = bwlimit.get(self.ctx)
302 # result of bwlimit.get is (ctx, share, minrate, maxrate)
307 def open(self, filename, mode = "r", bufsize = -1):
309 return self.chroot_call(open, filename, mode, bufsize)
311 def __do_chcontext(self, state_file):
314 print >>state_file, "%u" % self.ctx
317 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
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 self.__do_chcontext(None)
365 def start(self, wait, runlevel = 3):
366 self.vm_running = True
367 self.rlimits_changed = False
369 child_pid = os.fork()
376 # open state file to record vserver info
377 state_file = open("/var/run/vservers/%s" % self.name, "w")
379 # use /dev/null for stdin, /var/log/boot.log for stdout/err
382 os.open("/dev/null", os.O_RDONLY)
384 log = open("/var/log/boot.log", "w", 0)
387 print >>log, ("%s: starting the virtual server %s" %
388 (time.asctime(time.gmtime()), self.name))
390 # perform pre-init cleanup
391 self.__prep(runlevel, log)
393 # execute each init script in turn
394 # XXX - we don't support all scripts that vserver script does
395 self.__do_chcontext(state_file)
396 for cmd in self.INITSCRIPTS + [None]:
398 # enter vserver context
399 arg_subst = { 'runlevel': runlevel }
400 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
402 print >>log, "executing '%s'" % " ".join(cmd_args)
403 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
405 traceback.print_exc()
408 # we get here due to an exception in the top-level child process
409 except Exception, ex:
410 traceback.print_exc()
416 def set_resources(self):
418 """ Called when vserver context is entered for first time,
419 should be overridden by subclass. """
423 def init_disk_info(self):
424 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
425 (child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
427 line = child_stdout.readline()
429 sys.stderr.write(child_stderr.readline())
430 (space, inodes) = line.split()
431 self.disk_inodes = int(inodes)
432 self.disk_blocks = int(space)
433 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
435 return self.disk_blocks * 1024
437 def stop(self, signal = signal.SIGKILL):
438 vserverimpl.killall(self.ctx, signal)
439 self.vm_running = False
440 self.rlimits_changed = False
444 def create(vm_name, static = False, ctor = VServer):
448 options += ['--static']
449 runcmd.run('vuseradd', options + [vm_name])
450 vm_id = pwd.getpwnam(vm_name)[2]
452 return ctor(vm_name, vm_id)