1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.72 2007/08/02 16:01:59 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 if not (os.path.isdir(self.dir) and
66 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
67 raise NoSuchVServer, "%s does not exist" % self.dir
69 def get(self, option, default = None):
72 return self.cache[option]
74 f = open(os.path.join(self.dir, option), "r")
75 buf = f.read().rstrip()
79 if default is not None:
82 raise KeyError, "Key %s is not set for %s" % (option, self.name)
84 def update(self, option, value):
89 old_umask = os.umask(0022)
90 filename = os.path.join(self.dir, option)
92 os.makedirs(os.path.dirname(filename), 0755)
95 f = open(filename, 'w')
96 if isinstance(value, list):
97 f.write("%s\n" % "\n".join(value))
99 f.write("%s\n" % value)
105 def unset(self, option):
110 filename = os.path.join(self.dir, option)
113 os.removedirs(os.path.dirname(filename))
122 def add_to_cache(cache, dirname, fnames):
124 full_name = os.path.join(dirname, file)
125 if os.path.islink(full_name):
127 elif (os.path.isfile(full_name) and
128 os.access(full_name, os.R_OK)):
129 f = open(full_name, "r")
130 cache[full_name.replace(os.path.join(self.dir, ''),
131 '')] = f.read().rstrip()
133 os.path.walk(self.dir, add_to_cache, self.cache)
138 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
139 ('/etc/rc.d/rc', '%(runlevel)d')]
141 def __init__(self, name, vm_id = None, vm_running = None):
144 self.rlimits_changed = False
145 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
146 if not (os.path.isdir(self.dir) and
147 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
148 raise NoSuchVServer, "no such vserver: " + name
149 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
150 self.remove_caps = ~vserverimpl.CAP_SAFE;
152 vm_id = int(self.config.get('context'))
154 if vm_running == None:
155 vm_running = self.is_running()
156 self.vm_running = vm_running
158 def have_limits_changed(self):
159 return self.rlimits_changed
161 def set_rlimit_limit(self,type,hard,soft,minimum):
162 """Generic set resource limit function for vserver"""
166 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
167 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
168 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
169 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
170 self.rlimits_changed = self.rlimits_changed or changed
172 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
174 resource_type = RLIMITS[type]
176 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
178 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
180 def set_rlimit_config(self,type,hard,soft,minimum):
181 """Generic set resource limit function for vserver"""
182 if hard <> VC_LIM_KEEP:
183 self.config.update('rlimits/%s.hard' % type.lower(), hard)
184 if soft <> VC_LIM_KEEP:
185 self.config.update('rlimits/%s.soft' % type.lower(), soft)
186 if minimum <> VC_LIM_KEEP:
187 self.config.update('rlimits/%s.min' % type.lower(), minimum)
188 self.set_rlimit_limit(type,hard,soft,minimum)
190 def get_rlimit_limit(self,type):
191 """Generic get resource configuration function for vserver"""
193 resource_type = RLIMITS[type]
195 ret = vserverimpl.getrlimit(self.ctx,resource_type)
197 print "Unexpected error with getrlimit for context %d" % self.ctx
198 ret = self.get_rlimit_config(type)
201 def get_rlimit_config(self,type):
202 """Generic get resource configuration function for vserver"""
203 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
204 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
205 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
206 return (hard,soft,minimum)
208 def set_capabilities(self, capabilities):
209 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
211 def set_capabilities_config(self, capabilities):
212 self.config.update('bcapabilities', capabilities)
213 self.set_capabilities(capabilities)
215 def get_capabilities(self):
216 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
218 def get_capabilities_config(self):
219 return self.config.get('bcapabilities', '')
221 def set_ipaddresses(self, addresses):
222 vserverimpl.netremove(self.ctx, "all")
223 for a in addresses.split(","):
224 vserverimpl.netadd(self.ctx, a)
226 def set_ipaddresses_config(self, addresses):
228 for a in addresses.split(","):
229 self.config.update("interfaces/%d/ip" % i, a)
231 while self.config.unset("interfaces/%d/ip" % i):
233 self.set_ipaddresses(addresses)
235 def get_ipaddresses_config(self):
239 r = self.config.get("interfaces/%d/ip" % i, '')
246 def get_ipaddresses(self):
247 # No clean way to do this right now.
250 def __do_chroot(self):
251 self.config.cache_it()
255 def chroot_call(self, fn, *args):
257 cwd_fd = os.open(".", os.O_RDONLY)
259 root_fd = os.open("/", os.O_RDONLY)
272 def set_disklimit(self, block_limit):
273 # block_limit is in kB
276 vserverimpl.unsetdlimit(self.dir, self.ctx)
278 print "Unexpected error with unsetdlimit for context %d" % self.ctx
282 block_usage = vserverimpl.DLIMIT_KEEP
283 inode_usage = vserverimpl.DLIMIT_KEEP
285 # init_disk_info() must have been called to get usage values
286 block_usage = self.disk_blocks
287 inode_usage = self.disk_inodes
291 vserverimpl.setdlimit(self.dir,
296 vserverimpl.DLIMIT_INF, # inode limit
297 2) # %age reserved for root
299 print "Unexpected error with setdlimit for context %d" % self.ctx
302 self.config.update('dlimits/0/space_total', block_limit)
304 def is_running(self):
305 return vserverimpl.isrunning(self.ctx)
307 def get_disklimit(self):
310 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
311 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
313 if ex.errno != errno.ESRCH:
315 # get here if no vserver disk limit has been set for xid
320 def set_sched_config(self, cpu_share, sched_flags):
322 """ Write current CPU scheduler parameters to the vserver
323 configuration file. This method does not modify the kernel CPU
324 scheduling parameters for this context. """
326 if sched_flags & SCHED_CPU_GUARANTEED:
327 cpu_guaranteed = cpu_share
330 self.config.update('sched/fill-rate2', cpu_share)
331 self.config.update('sched/fill-rate', cpu_guaranteed)
334 self.set_sched(cpu_share, sched_flags)
336 def set_sched(self, cpu_share, sched_flags = 0):
337 """ Update kernel CPU scheduling parameters for this context. """
338 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
341 # have no way of querying scheduler right now on a per vserver basis
344 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
345 exempt_min = None, exempt_max = None,
346 share = None, dev = "eth0"):
349 bwlimit.off(self.ctx, dev)
351 bwlimit.on(self.ctx, dev, share,
352 minrate, maxrate, exempt_min, exempt_max)
354 def get_bwlimit(self, dev = "eth0"):
356 result = bwlimit.get(self.ctx)
357 # result of bwlimit.get is (ctx, share, minrate, maxrate)
362 def open(self, filename, mode = "r", bufsize = -1):
364 return self.chroot_call(open, filename, mode, bufsize)
366 def __do_chcontext(self, state_file):
369 print >>state_file, "%u" % self.ctx
372 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
374 vserverimpl.setup_done(self.ctx)
376 def __prep(self, runlevel, log):
378 """ Perform all the crap that the vserver script does before
379 actually executing the startup scripts. """
381 # remove /var/run and /var/lock/subsys files
382 # but don't remove utmp from the top-level /var/run
384 LOCKDIR = "/var/lock/subsys"
385 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
386 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
387 (out + map((dir + "/").__add__, ff(files)),
389 list(os.walk(RUNDIR)),
391 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
392 os.listdir(LOCKDIR)))
397 # set the initial runlevel
398 f = open(RUNDIR + "/utmp", "w")
399 utmp.set_runlevel(f, runlevel)
402 # mount /proc and /dev/pts
403 self.__do_mount("none", "/proc", "proc")
404 # XXX - magic mount options
405 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
407 def __do_mount(self, *mount_args):
410 mountimpl.mount(*mount_args)
412 if ex.errno == errno.EBUSY:
413 # assume already mounted
419 self.__do_chcontext(None)
421 def start(self, wait, runlevel = 3):
422 self.vm_running = True
423 self.rlimits_changed = False
425 child_pid = os.fork()
432 # open state file to record vserver info
433 state_file = open("/var/run/vservers/%s" % self.name, "w")
435 # use /dev/null for stdin, /var/log/boot.log for stdout/err
436 fd = os.open("/dev/null", os.O_RDONLY)
441 log = open("/var/log/boot.log", "w", 0)
442 if log.fileno() != 1:
443 os.dup2(log.fileno(), 1)
446 print >>log, ("%s: starting the virtual server %s" %
447 (time.asctime(time.gmtime()), self.name))
449 # perform pre-init cleanup
450 self.__prep(runlevel, log)
452 # execute each init script in turn
453 # XXX - we don't support all scripts that vserver script does
454 self.__do_chcontext(state_file)
455 for cmd in self.INITSCRIPTS:
457 # enter vserver context
458 arg_subst = { 'runlevel': runlevel }
459 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
461 print >>log, "executing '%s'" % " ".join(cmd_args)
462 os.spawnvp(os.P_WAIT,cmd[0],cmd_args)
464 traceback.print_exc()
467 # we get here due to an exception in the top-level child process
468 except Exception, ex:
469 traceback.print_exc()
475 def set_resources(self):
477 """ Called when vserver context is entered for first time,
478 should be overridden by subclass. """
482 def init_disk_info(self):
483 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
484 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
485 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
488 line = p.stdout.readline()
490 sys.stderr.write(p.stderr.read())
495 (space, inodes) = line.split()
496 self.disk_inodes = int(inodes)
497 self.disk_blocks = int(space)
498 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
500 return self.disk_blocks * 1024
502 def stop(self, signal = signal.SIGKILL):
503 vserverimpl.killall(self.ctx, signal)
504 self.vm_running = False
505 self.rlimits_changed = False
509 def create(vm_name, static = False, ctor = VServer):
513 options += ['--static']
514 runcmd.run('vuseradd', options + [vm_name])
515 vm_id = pwd.getpwnam(vm_name)[2]
517 return ctor(vm_name, vm_id)