1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.60 2007/07/17 18:55:25 dhozac Exp $
20 import cpulimit, bwlimit
22 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
23 from vserverimpl import DLIMIT_INF
24 from vserverimpl import VC_LIM_KEEP
26 from vserverimpl import RLIMIT_CPU
27 from vserverimpl import RLIMIT_RSS
28 from vserverimpl import RLIMIT_NPROC
29 from vserverimpl import RLIMIT_NOFILE
30 from vserverimpl import RLIMIT_MEMLOCK
31 from vserverimpl import RLIMIT_AS
32 from vserverimpl import RLIMIT_LOCKS
33 from vserverimpl import RLIMIT_SIGPENDING
34 from vserverimpl import RLIMIT_MSGQUEUE
35 from vserverimpl import VLIMIT_NSOCK
36 from vserverimpl import VLIMIT_OPENFD
37 from vserverimpl import VLIMIT_ANON
38 from vserverimpl import VLIMIT_SHMEM
41 # these are the flags taken from the kernel linux/vserver/legacy.h
44 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
52 RLIMITS = {"CPU": RLIMIT_CPU,
54 "NPROC": RLIMIT_NPROC,
55 "NOFILE": RLIMIT_NOFILE,
56 "MEMLOCK": RLIMIT_MEMLOCK,
58 "LOCKS": RLIMIT_LOCKS,
59 "SIGPENDING": RLIMIT_SIGPENDING,
60 "MSGQUEUE": RLIMIT_MSGQUEUE,
61 "NSOCK": VLIMIT_NSOCK,
62 "OPENFD": VLIMIT_OPENFD,
64 "SHMEM": VLIMIT_SHMEM}
66 class NoSuchVServer(Exception): pass
70 def __init__(self, name, directory):
74 def get(self, option, default = None):
76 f = open(os.path.join(self.dir, option), "r")
77 buf = f.readline().rstrip()
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 set_capabilities(self, capabilities):
181 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
183 def set_capabilities_config(self, capabilities):
184 self.config.update('bcapabilities', capabilities)
185 self.set_capabilities(capabilities)
187 def get_capabilities(self):
188 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
190 def get_capabilities_config(self):
191 return self.config.get('bcapabilities', '')
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 self.config.update('dlimits/0/space_total', block_limit)
247 def is_running(self):
248 return vserverimpl.isrunning(self.ctx)
250 def get_disklimit(self):
253 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
254 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
256 if ex.errno != errno.ESRCH:
258 # get here if no vserver disk limit has been set for xid
263 def set_sched_config(self, cpu_share, sched_flags):
265 """ Write current CPU scheduler parameters to the vserver
266 configuration file. This method does not modify the kernel CPU
267 scheduling parameters for this context. """
269 if sched_flags & SCHED_CPU_GUARANTEED:
270 cpu_guaranteed = cpu_share
273 self.config.update('sched/fill-rate2', cpu_share)
274 self.config.update('sched/fill-rate', cpu_guaranteed)
277 self.set_sched(cpu_share, sched_flags)
279 def set_sched(self, cpu_share, sched_flags = 0):
280 """ Update kernel CPU scheduling parameters for this context. """
281 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
284 # have no way of querying scheduler right now on a per vserver basis
287 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
288 exempt_min = None, exempt_max = None,
289 share = None, dev = "eth0"):
292 bwlimit.off(self.ctx, dev)
294 bwlimit.on(self.ctx, dev, share,
295 minrate, maxrate, exempt_min, exempt_max)
297 def get_bwlimit(self, dev = "eth0"):
299 result = bwlimit.get(self.ctx)
300 # result of bwlimit.get is (ctx, share, minrate, maxrate)
305 def open(self, filename, mode = "r", bufsize = -1):
307 return self.chroot_call(open, filename, mode, bufsize)
309 def __do_chcontext(self, state_file):
312 print >>state_file, "%u" % self.ctx
315 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
317 vserverimpl.setup_done(self.ctx)
319 def __prep(self, runlevel, log):
321 """ Perform all the crap that the vserver script does before
322 actually executing the startup scripts. """
324 # remove /var/run and /var/lock/subsys files
325 # but don't remove utmp from the top-level /var/run
327 LOCKDIR = "/var/lock/subsys"
328 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
329 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
330 (out + map((dir + "/").__add__, ff(files)),
332 list(os.walk(RUNDIR)),
334 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
335 os.listdir(LOCKDIR)))
339 # set the initial runlevel
340 f = open(RUNDIR + "/utmp", "w")
341 utmp.set_runlevel(f, runlevel)
344 # mount /proc and /dev/pts
345 self.__do_mount("none", "/proc", "proc")
346 # XXX - magic mount options
347 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
349 def __do_mount(self, *mount_args):
352 mountimpl.mount(*mount_args)
354 if ex.errno == errno.EBUSY:
355 # assume already mounted
361 self.__do_chcontext(None)
363 def start(self, wait, runlevel = 3):
364 self.vm_running = True
365 self.rlimits_changed = False
367 child_pid = os.fork()
374 # open state file to record vserver info
375 state_file = open("/var/run/vservers/%s" % self.name, "w")
377 # use /dev/null for stdin, /var/log/boot.log for stdout/err
380 os.open("/dev/null", os.O_RDONLY)
382 log = open("/var/log/boot.log", "w", 0)
385 print >>log, ("%s: starting the virtual server %s" %
386 (time.asctime(time.gmtime()), self.name))
388 # perform pre-init cleanup
389 self.__prep(runlevel, log)
391 # execute each init script in turn
392 # XXX - we don't support all scripts that vserver script does
393 self.__do_chcontext(state_file)
394 for cmd in self.INITSCRIPTS + [None]:
396 # enter vserver context
397 arg_subst = { 'runlevel': runlevel }
398 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
400 print >>log, "executing '%s'" % " ".join(cmd_args)
401 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
403 traceback.print_exc()
406 # we get here due to an exception in the top-level child process
407 except Exception, ex:
408 traceback.print_exc()
414 def set_resources(self):
416 """ Called when vserver context is entered for first time,
417 should be overridden by subclass. """
421 def init_disk_info(self):
422 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
423 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
424 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
427 line = p.stdout.readline()
429 sys.stderr.write(p.stderr.read())
434 (space, inodes) = line.split()
435 self.disk_inodes = int(inodes)
436 self.disk_blocks = int(space)
437 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
439 return self.disk_blocks * 1024
441 def stop(self, signal = signal.SIGKILL):
442 vserverimpl.killall(self.ctx, signal)
443 self.vm_running = False
444 self.rlimits_changed = False
448 def create(vm_name, static = False, ctor = VServer):
452 options += ['--static']
453 runcmd.run('vuseradd', options + [vm_name])
454 vm_id = pwd.getpwnam(vm_name)[2]
456 return ctor(vm_name, vm_id)