1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.72 2007/08/02 16:01:59 dhozac Exp $
18 import cpulimit, bwlimit
20 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
21 from vserverimpl import DLIMIT_INF
22 from vserverimpl import VC_LIM_KEEP
23 from vserverimpl import VLIMIT_NSOCK
24 from vserverimpl import VLIMIT_OPENFD
25 from vserverimpl import VLIMIT_ANON
26 from vserverimpl import VLIMIT_SHMEM
29 # these are the flags taken from the kernel linux/vserver/legacy.h
32 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
40 RLIMITS = { "NSOCK": VLIMIT_NSOCK,
41 "OPENFD": VLIMIT_OPENFD,
43 "SHMEM": VLIMIT_SHMEM}
45 # add in the platform supported rlimits
46 for entry in resource.__dict__.keys():
47 if entry.find("RLIMIT_")==0:
48 k = entry[len("RLIMIT_"):]
49 if not RLIMITS.has_key(k):
50 RLIMITS[k]=resource.__dict__[entry]
52 print "WARNING: duplicate RLIMITS key %s" % k
54 class NoSuchVServer(Exception): pass
58 def __init__(self, name, directory):
62 if not (os.path.isdir(self.dir) and
63 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
64 raise NoSuchVServer, "%s does not exist" % self.dir
66 def get(self, option, default = None):
69 return self.cache[option]
71 f = open(os.path.join(self.dir, option), "r")
72 buf = f.read().rstrip()
76 if default is not None:
79 raise KeyError, "Key %s is not set for %s" % (option, self.name)
81 def update(self, option, value):
86 old_umask = os.umask(0022)
87 filename = os.path.join(self.dir, option)
89 os.makedirs(os.path.dirname(filename), 0755)
92 f = open(filename, 'w')
93 if isinstance(value, list):
94 f.write("%s\n" % "\n".join(value))
96 f.write("%s\n" % value)
102 def unset(self, option):
107 filename = os.path.join(self.dir, option)
110 os.removedirs(os.path.dirname(filename))
119 def add_to_cache(cache, dirname, fnames):
121 full_name = os.path.join(dirname, file)
122 if os.path.islink(full_name):
124 elif (os.path.isfile(full_name) and
125 os.access(full_name, os.R_OK)):
126 f = open(full_name, "r")
127 cache[full_name.replace(os.path.join(self.dir, ''),
128 '')] = f.read().rstrip()
130 os.path.walk(self.dir, add_to_cache, self.cache)
135 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
136 ('/etc/rc.d/rc', '%(runlevel)d')]
138 def __init__(self, name, vm_id = None, vm_running = None):
141 self.rlimits_changed = False
142 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
143 if not (os.path.isdir(self.dir) and
144 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
145 raise NoSuchVServer, "no such vserver: " + name
146 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
147 self.remove_caps = ~vserverimpl.CAP_SAFE;
149 vm_id = int(self.config.get('context'))
151 if vm_running == None:
152 vm_running = self.is_running()
153 self.vm_running = vm_running
155 def have_limits_changed(self):
156 return self.rlimits_changed
158 def set_rlimit_limit(self,type,hard,soft,minimum):
159 """Generic set resource limit function for vserver"""
163 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
164 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
165 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
166 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
167 self.rlimits_changed = self.rlimits_changed or changed
169 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
171 resource_type = RLIMITS[type]
173 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
175 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
177 def set_rlimit_config(self,type,hard,soft,minimum):
178 """Generic set resource limit function for vserver"""
179 if hard <> VC_LIM_KEEP:
180 self.config.update('rlimits/%s.hard' % type.lower(), hard)
181 if soft <> VC_LIM_KEEP:
182 self.config.update('rlimits/%s.soft' % type.lower(), soft)
183 if minimum <> VC_LIM_KEEP:
184 self.config.update('rlimits/%s.min' % type.lower(), minimum)
185 self.set_rlimit_limit(type,hard,soft,minimum)
187 def get_rlimit_limit(self,type):
188 """Generic get resource configuration function for vserver"""
190 resource_type = RLIMITS[type]
192 ret = vserverimpl.getrlimit(self.ctx,resource_type)
194 print "Unexpected error with getrlimit for context %d" % self.ctx
195 ret = self.get_rlimit_config(type)
198 def get_rlimit_config(self,type):
199 """Generic get resource configuration function for vserver"""
200 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
201 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
202 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
203 return (hard,soft,minimum)
205 def set_capabilities(self, capabilities):
206 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
208 def set_capabilities_config(self, capabilities):
209 self.config.update('bcapabilities', capabilities)
210 self.set_capabilities(capabilities)
212 def get_capabilities(self):
213 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
215 def get_capabilities_config(self):
216 return self.config.get('bcapabilities', '')
218 def set_ipaddresses(self, addresses):
219 vserverimpl.netremove(self.ctx, "all")
220 for a in addresses.split(","):
221 vserverimpl.netadd(self.ctx, a)
223 def set_ipaddresses_config(self, addresses):
225 for a in addresses.split(","):
226 self.config.update("interfaces/%d/ip" % i, a)
228 while self.config.unset("interfaces/%d/ip" % i):
230 self.set_ipaddresses(addresses)
232 def get_ipaddresses_config(self):
236 r = self.config.get("interfaces/%d/ip" % i, '')
243 def get_ipaddresses(self):
244 # No clean way to do this right now.
247 def __do_chroot(self):
251 def chroot_call(self, fn, *args):
253 cwd_fd = os.open(".", os.O_RDONLY)
255 root_fd = os.open("/", os.O_RDONLY)
268 def set_disklimit(self, block_limit):
269 # block_limit is in kB
272 vserverimpl.unsetdlimit(self.dir, self.ctx)
274 print "Unexpected error with unsetdlimit for context %d" % self.ctx
278 block_usage = vserverimpl.DLIMIT_KEEP
279 inode_usage = vserverimpl.DLIMIT_KEEP
281 # init_disk_info() must have been called to get usage values
282 block_usage = self.disk_blocks
283 inode_usage = self.disk_inodes
287 vserverimpl.setdlimit(self.dir,
292 vserverimpl.DLIMIT_INF, # inode limit
293 2) # %age reserved for root
295 print "Unexpected error with setdlimit for context %d" % self.ctx
298 self.config.update('dlimits/0/space_total', block_limit)
300 def is_running(self):
301 return vserverimpl.isrunning(self.ctx)
303 def get_disklimit(self):
306 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
307 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
309 if ex.errno != errno.ESRCH:
311 # get here if no vserver disk limit has been set for xid
316 def set_sched_config(self, cpu_share, sched_flags):
318 """ Write current CPU scheduler parameters to the vserver
319 configuration file. This method does not modify the kernel CPU
320 scheduling parameters for this context. """
322 if sched_flags & SCHED_CPU_GUARANTEED:
323 cpu_guaranteed = cpu_share
326 self.config.update('sched/fill-rate2', cpu_share)
327 self.config.update('sched/fill-rate', cpu_guaranteed)
330 self.set_sched(cpu_share, sched_flags)
332 def set_sched(self, cpu_share, sched_flags = 0):
333 """ Update kernel CPU scheduling parameters for this context. """
334 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
337 # have no way of querying scheduler right now on a per vserver basis
340 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
341 exempt_min = None, exempt_max = None,
342 share = None, dev = "eth0"):
345 bwlimit.off(self.ctx, dev)
347 bwlimit.on(self.ctx, dev, share,
348 minrate, maxrate, exempt_min, exempt_max)
350 def get_bwlimit(self, dev = "eth0"):
352 result = bwlimit.get(self.ctx)
353 # result of bwlimit.get is (ctx, share, minrate, maxrate)
358 def open(self, filename, mode = "r", bufsize = -1):
360 return self.chroot_call(open, filename, mode, bufsize)
362 def __do_chcontext(self, state_file):
365 print >>state_file, "%u" % self.ctx
368 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
370 vserverimpl.setup_done(self.ctx)
372 def __prep(self, runlevel, log):
374 """ Perform all the crap that the vserver script does before
375 actually executing the startup scripts. """
377 # remove /var/run and /var/lock/subsys files
378 # but don't remove utmp from the top-level /var/run
380 LOCKDIR = "/var/lock/subsys"
381 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
382 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
383 (out + map((dir + "/").__add__, ff(files)),
385 list(os.walk(RUNDIR)),
387 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
388 os.listdir(LOCKDIR)))
393 # set the initial runlevel
394 vserverimpl.setrunlevel(RUNDIR + "/utmp", runlevel)
396 # mount /proc and /dev/pts
397 self.__do_mount("none", self.dir, "/proc", "proc")
398 # XXX - magic mount options
399 self.__do_mount("none", self.dir, "/dev/pts", "devpts", 0, "gid=5,mode=0620")
401 def __do_mount(self, *mount_args):
404 vserverimpl.mount(*mount_args)
406 if ex.errno == errno.EBUSY:
407 # assume already mounted
412 self.config.cache_it()
414 self.__do_chcontext(None)
416 def start(self, wait, runlevel = 3):
417 self.vm_running = True
418 self.rlimits_changed = False
420 child_pid = os.fork()
427 # open state file to record vserver info
428 state_file = open("/var/run/vservers/%s" % self.name, "w")
430 # use /dev/null for stdin, /var/log/boot.log for stdout/err
431 fd = os.open("/dev/null", os.O_RDONLY)
435 self.config.cache_it()
437 log = open("/var/log/boot.log", "w", 0)
438 if log.fileno() != 1:
439 os.dup2(log.fileno(), 1)
442 print >>log, ("%s: starting the virtual server %s" %
443 (time.asctime(time.gmtime()), self.name))
445 # perform pre-init cleanup
446 self.__prep(runlevel, log)
448 # execute each init script in turn
449 # XXX - we don't support all scripts that vserver script does
450 self.__do_chcontext(state_file)
451 for cmd in self.INITSCRIPTS:
453 # enter vserver context
454 arg_subst = { 'runlevel': runlevel }
455 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
457 print >>log, "executing '%s'" % " ".join(cmd_args)
458 os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args)
460 traceback.print_exc()
463 # we get here due to an exception in the top-level child process
464 except Exception, ex:
465 traceback.print_exc()
471 def set_resources(self):
473 """ Called when vserver context is entered for first time,
474 should be overridden by subclass. """
478 def init_disk_info(self):
479 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
480 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
481 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
484 line = p.stdout.readline()
486 sys.stderr.write(p.stderr.read())
491 (space, inodes) = line.split()
492 self.disk_inodes = int(inodes)
493 self.disk_blocks = int(space)
494 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
496 return self.disk_blocks * 1024
498 def stop(self, signal = signal.SIGKILL):
499 vserverimpl.killall(self.ctx, signal)
500 self.vm_running = False
501 self.rlimits_changed = False
505 def create(vm_name, static = False, ctor = VServer):
507 options = ['vuseradd']
509 options += ['--static']
510 ret = os.spawnvp(os.P_WAIT, 'vuseradd', options + [vm_name])
511 if not os.WIFEXITED(ret) or os.WEXITSTATUS(ret) != 0:
512 out = "system command ('%s') " % options
513 if os.WIFEXITED(ret):
514 out += "failed, rc = %d" % os.WEXITSTATUS(ret)
516 out += "killed by signal %d" % os.WTERMSIG(ret)
517 raise SystemError, out
518 vm_id = pwd.getpwnam(vm_name)[2]
520 return ctor(vm_name, vm_id)