1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.68 2007/07/31 22:04:24 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):
64 if not (os.path.isdir(self.dir) and
65 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK):
66 raise NoSuchVServer, "%s does not exist" % self.dir
68 def get(self, option, default = None):
70 f = open(os.path.join(self.dir, option), "r")
71 buf = f.readline().rstrip()
75 if default is not None:
78 raise KeyError, "Key %s is not set for %s" % (option, self.name)
80 def update(self, option, value):
82 old_umask = os.umask(0022)
83 filename = os.path.join(self.dir, option)
85 os.makedirs(os.path.dirname(filename), 0755)
88 f = open(filename, 'w')
89 if isinstance(value, list):
90 f.write("%s\n" % "\n".join(value))
92 f.write("%s\n" % value)
98 def unset(self, option):
100 filename = os.path.join(self.dir, option)
102 os.removedirs(os.path.dirname(filename))
110 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
111 ('/etc/rc.d/rc', '%(runlevel)d')]
113 def __init__(self, name, vm_id = None, vm_running = None):
116 self.rlimits_changed = False
117 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
118 if not (os.path.isdir(self.dir) and
119 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
120 raise NoSuchVServer, "no such vserver: " + name
121 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
122 self.remove_caps = ~vserverimpl.CAP_SAFE;
124 vm_id = int(self.config.get('context'))
126 if vm_running == None:
127 vm_running = self.is_running()
128 self.vm_running = vm_running
130 def have_limits_changed(self):
131 return self.rlimits_changed
133 def set_rlimit_limit(self,type,hard,soft,minimum):
134 """Generic set resource limit function for vserver"""
138 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
139 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
140 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
141 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
142 self.rlimits_changed = self.rlimits_changed or changed
144 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
146 resource_type = RLIMITS[type]
148 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
150 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
152 def set_rlimit_config(self,type,hard,soft,minimum):
153 """Generic set resource limit function for vserver"""
154 if hard <> VC_LIM_KEEP:
155 self.config.update('rlimits/%s.hard' % type.lower(), hard)
156 if soft <> VC_LIM_KEEP:
157 self.config.update('rlimits/%s.soft' % type.lower(), soft)
158 if minimum <> VC_LIM_KEEP:
159 self.config.update('rlimits/%s.min' % type.lower(), minimum)
160 self.set_rlimit_limit(type,hard,soft,minimum)
162 def get_rlimit_limit(self,type):
163 """Generic get resource configuration function for vserver"""
165 resource_type = RLIMITS[type]
167 ret = vserverimpl.getrlimit(self.ctx,resource_type)
169 print "Unexpected error with getrlimit for context %d" % self.ctx
170 ret = self.get_rlimit_config(type)
173 def get_rlimit_config(self,type):
174 """Generic get resource configuration function for vserver"""
175 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
176 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
177 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
178 return (hard,soft,minimum)
180 def set_WHITELISTED_config(self,whitelisted):
181 self.config.update('whitelisted', whitelisted)
183 def set_capabilities(self, capabilities):
184 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
186 def set_capabilities_config(self, capabilities):
187 self.config.update('bcapabilities', capabilities)
188 self.set_capabilities(capabilities)
190 def get_capabilities(self):
191 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
193 def get_capabilities_config(self):
194 return self.config.get('bcapabilities', '')
196 def set_ipaddresses(self, addresses):
197 vserverimpl.netremove(self.ctx, "all")
198 for a in addresses.split(","):
199 vserverimpl.netadd(self.ctx, a)
201 def set_ipaddresses_config(self, addresses):
203 for a in addresses.split(","):
204 self.config.update("interfaces/%d/ip" % i, a)
206 while self.config.unset("interfaces/%d/ip" % i):
208 self.set_ipaddresses(addresses)
210 def get_ipaddresses_config(self):
214 r = self.config.get("interfaces/%d/ip" % i, '')
221 def get_ipaddresses(self):
222 # No clean way to do this right now.
225 def __do_chroot(self):
230 def chroot_call(self, fn, *args):
232 cwd_fd = os.open(".", os.O_RDONLY)
234 root_fd = os.open("/", os.O_RDONLY)
247 def set_disklimit(self, block_limit):
248 # block_limit is in kB
251 vserverimpl.unsetdlimit(self.dir, self.ctx)
253 print "Unexpected error with unsetdlimit for context %d" % self.ctx
257 block_usage = vserverimpl.DLIMIT_KEEP
258 inode_usage = vserverimpl.DLIMIT_KEEP
260 # init_disk_info() must have been called to get usage values
261 block_usage = self.disk_blocks
262 inode_usage = self.disk_inodes
266 vserverimpl.setdlimit(self.dir,
271 vserverimpl.DLIMIT_INF, # inode limit
272 2) # %age reserved for root
274 print "Unexpected error with setdlimit for context %d" % self.ctx
277 self.config.update('dlimits/0/space_total', block_limit)
279 def is_running(self):
280 return vserverimpl.isrunning(self.ctx)
282 def get_disklimit(self):
285 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
286 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
288 if ex.errno != errno.ESRCH:
290 # get here if no vserver disk limit has been set for xid
295 def set_sched_config(self, cpu_share, sched_flags):
297 """ Write current CPU scheduler parameters to the vserver
298 configuration file. This method does not modify the kernel CPU
299 scheduling parameters for this context. """
301 if sched_flags & SCHED_CPU_GUARANTEED:
302 cpu_guaranteed = cpu_share
305 self.config.update('sched/fill-rate2', cpu_share)
306 self.config.update('sched/fill-rate', cpu_guaranteed)
309 self.set_sched(cpu_share, sched_flags)
311 def set_sched(self, cpu_share, sched_flags = 0):
312 """ Update kernel CPU scheduling parameters for this context. """
313 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
316 # have no way of querying scheduler right now on a per vserver basis
319 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
320 exempt_min = None, exempt_max = None,
321 share = None, dev = "eth0"):
324 bwlimit.off(self.ctx, dev)
326 bwlimit.on(self.ctx, dev, share,
327 minrate, maxrate, exempt_min, exempt_max)
329 def get_bwlimit(self, dev = "eth0"):
331 result = bwlimit.get(self.ctx)
332 # result of bwlimit.get is (ctx, share, minrate, maxrate)
337 def open(self, filename, mode = "r", bufsize = -1):
339 return self.chroot_call(open, filename, mode, bufsize)
341 def __do_chcontext(self, state_file):
344 print >>state_file, "%u" % self.ctx
347 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
349 vserverimpl.setup_done(self.ctx)
351 def __prep(self, runlevel, log):
353 """ Perform all the crap that the vserver script does before
354 actually executing the startup scripts. """
356 # remove /var/run and /var/lock/subsys files
357 # but don't remove utmp from the top-level /var/run
359 LOCKDIR = "/var/lock/subsys"
360 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
361 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
362 (out + map((dir + "/").__add__, ff(files)),
364 list(os.walk(RUNDIR)),
366 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
367 os.listdir(LOCKDIR)))
372 # set the initial runlevel
373 f = open(RUNDIR + "/utmp", "w")
374 utmp.set_runlevel(f, runlevel)
377 # mount /proc and /dev/pts
378 self.__do_mount("none", "/proc", "proc")
379 # XXX - magic mount options
380 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
382 def __do_mount(self, *mount_args):
385 mountimpl.mount(*mount_args)
387 if ex.errno == errno.EBUSY:
388 # assume already mounted
394 self.__do_chcontext(None)
396 def start(self, wait, runlevel = 3):
397 self.vm_running = True
398 self.rlimits_changed = False
400 child_pid = os.fork()
407 # open state file to record vserver info
408 state_file = open("/var/run/vservers/%s" % self.name, "w")
410 # use /dev/null for stdin, /var/log/boot.log for stdout/err
411 fd = os.open("/dev/null", os.O_RDONLY)
416 log = open("/var/log/boot.log", "w", 0)
417 if log.fileno() != 1:
418 os.dup2(log.fileno(), 1)
421 print >>log, ("%s: starting the virtual server %s" %
422 (time.asctime(time.gmtime()), self.name))
424 # perform pre-init cleanup
425 self.__prep(runlevel, log)
427 # execute each init script in turn
428 # XXX - we don't support all scripts that vserver script does
429 self.__do_chcontext(state_file)
430 for cmd in self.INITSCRIPTS + [None]:
432 # enter vserver context
433 arg_subst = { 'runlevel': runlevel }
434 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
436 print >>log, "executing '%s'" % " ".join(cmd_args)
437 os.spawnvp(os.P_WAIT,cmd[0],cmd_args)
439 traceback.print_exc()
442 # we get here due to an exception in the top-level child process
443 except Exception, ex:
444 traceback.print_exc()
450 def set_resources(self):
452 """ Called when vserver context is entered for first time,
453 should be overridden by subclass. """
457 def init_disk_info(self):
458 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
459 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
460 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
463 line = p.stdout.readline()
465 sys.stderr.write(p.stderr.read())
470 (space, inodes) = line.split()
471 self.disk_inodes = int(inodes)
472 self.disk_blocks = int(space)
473 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
475 return self.disk_blocks * 1024
477 def stop(self, signal = signal.SIGKILL):
478 vserverimpl.killall(self.ctx, signal)
479 self.vm_running = False
480 self.rlimits_changed = False
484 def create(vm_name, static = False, ctor = VServer):
488 options += ['--static']
489 runcmd.run('vuseradd', options + [vm_name])
490 vm_id = pwd.getpwnam(vm_name)[2]
492 return ctor(vm_name, vm_id)