1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.62 2007/07/20 19:45:35 dhozac Exp $
21 import cpulimit, bwlimit
23 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
24 from vserverimpl import DLIMIT_INF
25 from vserverimpl import VC_LIM_KEEP
26 from vserverimpl import VLIMIT_NSOCK
27 from vserverimpl import VLIMIT_OPENFD
28 from vserverimpl import VLIMIT_ANON
29 from vserverimpl import VLIMIT_SHMEM
32 # these are the flags taken from the kernel linux/vserver/legacy.h
35 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
43 RLIMITS = { "NSOCK": VLIMIT_NSOCK,
44 "OPENFD": VLIMIT_OPENFD,
46 "SHMEM": VLIMIT_SHMEM}
48 # add in the platform supported rlimits
49 for entry in resource.__dict__.keys():
50 if entry.find("RLIMIT_")==0:
51 k = entry[len("RLIMIT_"):]
52 if not RLIMITS.has_key(k):
53 RLIMITS[k]=resource.__dict__[entry]
55 print "WARNING: duplicate RLIMITS key %s" % k
57 class NoSuchVServer(Exception): pass
61 def __init__(self, name, directory):
65 def get(self, option, default = None):
67 f = open(os.path.join(self.dir, option), "r")
68 buf = f.readline().rstrip()
72 if default is not None:
75 raise KeyError, "Key %s is not set for %s" % (option, self.name)
77 def update(self, option, value):
79 old_umask = os.umask(0022)
80 filename = os.path.join(self.dir, option)
82 os.makedirs(os.path.dirname(filename), 0755)
85 f = open(filename, 'w')
86 if isinstance(value, list):
87 f.write("%s\n" % "\n".join(value))
89 f.write("%s\n" % value)
93 raise KeyError, "Don't know how to handle %s, sorry" % option
98 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
99 ('/etc/rc.d/rc', '%(runlevel)d')]
101 def __init__(self, name, vm_id = None, vm_running = None):
104 self.rlimits_changed = False
105 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
106 if not (os.path.isdir(self.dir) and
107 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
108 raise NoSuchVServer, "no such vserver: " + name
109 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
110 self.remove_caps = ~vserverimpl.CAP_SAFE;
112 vm_id = int(self.config.get('context'))
114 if vm_running == None:
115 vm_running = self.is_running()
116 self.vm_running = vm_running
118 def have_limits_changed(self):
119 return self.rlimits_changed
121 def set_rlimit_limit(self,type,hard,soft,minimum):
122 """Generic set resource limit function for vserver"""
126 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
127 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
128 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
129 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
130 self.rlimits_changed = self.rlimits_changed or changed
132 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
134 resource_type = RLIMITS[type]
136 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
138 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
140 def set_rlimit_config(self,type,hard,soft,minimum):
141 """Generic set resource limit function for vserver"""
142 if hard <> VC_LIM_KEEP:
143 self.config.update('rlimits/%s.hard' % type.lower(), hard)
144 if soft <> VC_LIM_KEEP:
145 self.config.update('rlimits/%s.soft' % type.lower(), soft)
146 if minimum <> VC_LIM_KEEP:
147 self.config.update('rlimits/%s.min' % type.lower(), minimum)
148 self.set_rlimit_limit(type,hard,soft,minimum)
150 def get_rlimit_limit(self,type):
151 """Generic get resource configuration function for vserver"""
153 resource_type = RLIMITS[type]
155 ret = vserverimpl.getrlimit(self.ctx,resource_type)
157 print "Unexpected error with getrlimit for context %d" % self.ctx
158 ret = self.get_rlimit_config(type)
161 def get_rlimit_config(self,type):
162 """Generic get resource configuration function for vserver"""
163 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
164 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
165 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
166 return (hard,soft,minimum)
168 def set_WHITELISTED_config(self,whitelisted):
169 self.config.update('whitelisted', whitelisted)
171 def set_capabilities(self, capabilities):
172 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
174 def set_capabilities_config(self, capabilities):
175 self.config.update('bcapabilities', capabilities)
176 self.set_capabilities(capabilities)
178 def get_capabilities(self):
179 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
181 def get_capabilities_config(self):
182 return self.config.get('bcapabilities', '')
184 def __do_chroot(self):
189 def chroot_call(self, fn, *args):
191 cwd_fd = os.open(".", os.O_RDONLY)
193 root_fd = os.open("/", os.O_RDONLY)
206 def set_disklimit(self, block_limit):
207 # block_limit is in kB
210 vserverimpl.unsetdlimit(self.dir, self.ctx)
212 print "Unexpected error with unsetdlimit for context %d" % self.ctx
216 block_usage = vserverimpl.DLIMIT_KEEP
217 inode_usage = vserverimpl.DLIMIT_KEEP
219 # init_disk_info() must have been called to get usage values
220 block_usage = self.disk_blocks
221 inode_usage = self.disk_inodes
225 vserverimpl.setdlimit(self.dir,
230 vserverimpl.DLIMIT_INF, # inode limit
231 2) # %age reserved for root
233 print "Unexpected error with setdlimit for context %d" % self.ctx
236 self.config.update('dlimits/0/space_total', block_limit)
238 def is_running(self):
239 return vserverimpl.isrunning(self.ctx)
241 def get_disklimit(self):
244 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
245 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
247 if ex.errno != errno.ESRCH:
249 # get here if no vserver disk limit has been set for xid
254 def set_sched_config(self, cpu_share, sched_flags):
256 """ Write current CPU scheduler parameters to the vserver
257 configuration file. This method does not modify the kernel CPU
258 scheduling parameters for this context. """
260 if sched_flags & SCHED_CPU_GUARANTEED:
261 cpu_guaranteed = cpu_share
264 self.config.update('sched/fill-rate2', cpu_share)
265 self.config.update('sched/fill-rate', cpu_guaranteed)
268 self.set_sched(cpu_share, sched_flags)
270 def set_sched(self, cpu_share, sched_flags = 0):
271 """ Update kernel CPU scheduling parameters for this context. """
272 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
275 # have no way of querying scheduler right now on a per vserver basis
278 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
279 exempt_min = None, exempt_max = None,
280 share = None, dev = "eth0"):
283 bwlimit.off(self.ctx, dev)
285 bwlimit.on(self.ctx, dev, share,
286 minrate, maxrate, exempt_min, exempt_max)
288 def get_bwlimit(self, dev = "eth0"):
290 result = bwlimit.get(self.ctx)
291 # result of bwlimit.get is (ctx, share, minrate, maxrate)
296 def open(self, filename, mode = "r", bufsize = -1):
298 return self.chroot_call(open, filename, mode, bufsize)
300 def __do_chcontext(self, state_file):
303 print >>state_file, "%u" % self.ctx
306 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
308 vserverimpl.setup_done(self.ctx)
310 def __prep(self, runlevel, log):
312 """ Perform all the crap that the vserver script does before
313 actually executing the startup scripts. """
315 # remove /var/run and /var/lock/subsys files
316 # but don't remove utmp from the top-level /var/run
318 LOCKDIR = "/var/lock/subsys"
319 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
320 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
321 (out + map((dir + "/").__add__, ff(files)),
323 list(os.walk(RUNDIR)),
325 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
326 os.listdir(LOCKDIR)))
331 # set the initial runlevel
332 f = open(RUNDIR + "/utmp", "w")
333 utmp.set_runlevel(f, runlevel)
336 # mount /proc and /dev/pts
337 self.__do_mount("none", "/proc", "proc")
338 # XXX - magic mount options
339 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
341 def __do_mount(self, *mount_args):
344 mountimpl.mount(*mount_args)
346 if ex.errno == errno.EBUSY:
347 # assume already mounted
353 self.__do_chcontext(None)
355 def start(self, wait, runlevel = 3):
356 self.vm_running = True
357 self.rlimits_changed = False
359 child_pid = os.fork()
366 # open state file to record vserver info
367 state_file = open("/var/run/vservers/%s" % self.name, "w")
369 # use /dev/null for stdin, /var/log/boot.log for stdout/err
372 os.open("/dev/null", os.O_RDONLY)
374 log = open("/var/log/boot.log", "w", 0)
377 print >>log, ("%s: starting the virtual server %s" %
378 (time.asctime(time.gmtime()), self.name))
380 # perform pre-init cleanup
381 self.__prep(runlevel, log)
383 # execute each init script in turn
384 # XXX - we don't support all scripts that vserver script does
385 self.__do_chcontext(state_file)
386 for cmd in self.INITSCRIPTS + [None]:
388 # enter vserver context
389 arg_subst = { 'runlevel': runlevel }
390 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
392 print >>log, "executing '%s'" % " ".join(cmd_args)
393 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
395 traceback.print_exc()
398 # we get here due to an exception in the top-level child process
399 except Exception, ex:
400 traceback.print_exc()
406 def set_resources(self):
408 """ Called when vserver context is entered for first time,
409 should be overridden by subclass. """
413 def init_disk_info(self):
414 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
415 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
416 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
419 line = p.stdout.readline()
421 sys.stderr.write(p.stderr.read())
426 (space, inodes) = line.split()
427 self.disk_inodes = int(inodes)
428 self.disk_blocks = int(space)
429 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
431 return self.disk_blocks * 1024
433 def stop(self, signal = signal.SIGKILL):
434 vserverimpl.killall(self.ctx, signal)
435 self.vm_running = False
436 self.rlimits_changed = False
440 def create(vm_name, static = False, ctor = VServer):
444 options += ['--static']
445 runcmd.run('vuseradd', options + [vm_name])
446 vm_id = pwd.getpwnam(vm_name)[2]
448 return ctor(vm_name, vm_id)