1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.63 2007/07/24 17:22:37 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 set_ipaddresses(self, addresses):
185 vserverimpl.netremove(self.ctx, "all")
186 for a in addresses.split(","):
187 vserverimpl.netadd(self.ctx, a)
189 def set_ipaddresses_config(self, addresses):
191 for a in addresses.split(","):
192 self.config.set("interfaces/%d/ip" % i, a)
194 self.set_ipaddresses(addresses)
196 def get_ipaddresses_config(self):
200 r = self.config.get("interfaces/%d/ip" % i, '')
207 def get_ipaddresses(self):
208 # No clean way to do this right now.
211 def __do_chroot(self):
216 def chroot_call(self, fn, *args):
218 cwd_fd = os.open(".", os.O_RDONLY)
220 root_fd = os.open("/", os.O_RDONLY)
233 def set_disklimit(self, block_limit):
234 # block_limit is in kB
237 vserverimpl.unsetdlimit(self.dir, self.ctx)
239 print "Unexpected error with unsetdlimit for context %d" % self.ctx
243 block_usage = vserverimpl.DLIMIT_KEEP
244 inode_usage = vserverimpl.DLIMIT_KEEP
246 # init_disk_info() must have been called to get usage values
247 block_usage = self.disk_blocks
248 inode_usage = self.disk_inodes
252 vserverimpl.setdlimit(self.dir,
257 vserverimpl.DLIMIT_INF, # inode limit
258 2) # %age reserved for root
260 print "Unexpected error with setdlimit for context %d" % self.ctx
263 self.config.update('dlimits/0/space_total', block_limit)
265 def is_running(self):
266 return vserverimpl.isrunning(self.ctx)
268 def get_disklimit(self):
271 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
272 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
274 if ex.errno != errno.ESRCH:
276 # get here if no vserver disk limit has been set for xid
281 def set_sched_config(self, cpu_share, sched_flags):
283 """ Write current CPU scheduler parameters to the vserver
284 configuration file. This method does not modify the kernel CPU
285 scheduling parameters for this context. """
287 if sched_flags & SCHED_CPU_GUARANTEED:
288 cpu_guaranteed = cpu_share
291 self.config.update('sched/fill-rate2', cpu_share)
292 self.config.update('sched/fill-rate', cpu_guaranteed)
295 self.set_sched(cpu_share, sched_flags)
297 def set_sched(self, cpu_share, sched_flags = 0):
298 """ Update kernel CPU scheduling parameters for this context. """
299 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
302 # have no way of querying scheduler right now on a per vserver basis
305 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
306 exempt_min = None, exempt_max = None,
307 share = None, dev = "eth0"):
310 bwlimit.off(self.ctx, dev)
312 bwlimit.on(self.ctx, dev, share,
313 minrate, maxrate, exempt_min, exempt_max)
315 def get_bwlimit(self, dev = "eth0"):
317 result = bwlimit.get(self.ctx)
318 # result of bwlimit.get is (ctx, share, minrate, maxrate)
323 def open(self, filename, mode = "r", bufsize = -1):
325 return self.chroot_call(open, filename, mode, bufsize)
327 def __do_chcontext(self, state_file):
330 print >>state_file, "%u" % self.ctx
333 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
335 vserverimpl.setup_done(self.ctx)
337 def __prep(self, runlevel, log):
339 """ Perform all the crap that the vserver script does before
340 actually executing the startup scripts. """
342 # remove /var/run and /var/lock/subsys files
343 # but don't remove utmp from the top-level /var/run
345 LOCKDIR = "/var/lock/subsys"
346 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
347 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
348 (out + map((dir + "/").__add__, ff(files)),
350 list(os.walk(RUNDIR)),
352 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
353 os.listdir(LOCKDIR)))
358 # set the initial runlevel
359 f = open(RUNDIR + "/utmp", "w")
360 utmp.set_runlevel(f, runlevel)
363 # mount /proc and /dev/pts
364 self.__do_mount("none", "/proc", "proc")
365 # XXX - magic mount options
366 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
368 def __do_mount(self, *mount_args):
371 mountimpl.mount(*mount_args)
373 if ex.errno == errno.EBUSY:
374 # assume already mounted
380 self.__do_chcontext(None)
382 def start(self, wait, runlevel = 3):
383 self.vm_running = True
384 self.rlimits_changed = False
386 child_pid = os.fork()
393 # open state file to record vserver info
394 state_file = open("/var/run/vservers/%s" % self.name, "w")
396 # use /dev/null for stdin, /var/log/boot.log for stdout/err
399 os.open("/dev/null", os.O_RDONLY)
401 log = open("/var/log/boot.log", "w", 0)
404 print >>log, ("%s: starting the virtual server %s" %
405 (time.asctime(time.gmtime()), self.name))
407 # perform pre-init cleanup
408 self.__prep(runlevel, log)
410 # execute each init script in turn
411 # XXX - we don't support all scripts that vserver script does
412 self.__do_chcontext(state_file)
413 for cmd in self.INITSCRIPTS + [None]:
415 # enter vserver context
416 arg_subst = { 'runlevel': runlevel }
417 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
419 print >>log, "executing '%s'" % " ".join(cmd_args)
420 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
422 traceback.print_exc()
425 # we get here due to an exception in the top-level child process
426 except Exception, ex:
427 traceback.print_exc()
433 def set_resources(self):
435 """ Called when vserver context is entered for first time,
436 should be overridden by subclass. """
440 def init_disk_info(self):
441 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
442 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
443 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
446 line = p.stdout.readline()
448 sys.stderr.write(p.stderr.read())
453 (space, inodes) = line.split()
454 self.disk_inodes = int(inodes)
455 self.disk_blocks = int(space)
456 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
458 return self.disk_blocks * 1024
460 def stop(self, signal = signal.SIGKILL):
461 vserverimpl.killall(self.ctx, signal)
462 self.vm_running = False
463 self.rlimits_changed = False
467 def create(vm_name, static = False, ctor = VServer):
471 options += ['--static']
472 runcmd.run('vuseradd', options + [vm_name])
473 vm_id = pwd.getpwnam(vm_name)[2]
475 return ctor(vm_name, vm_id)