1 # Copyright 2005 Princeton University
16 import vserverimpl, vduimpl
17 import cpulimit, bwlimit
19 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
20 from vserverimpl import DLIMIT_INF
25 # these are the flags taken from the kernel linux/vserver/legacy.h
28 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
38 class NoSuchVServer(Exception): pass
44 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
45 ('/etc/rc.d/rc', '%(runlevel)d')]
47 def __init__(self, name, vm_id = None, vm_running = False):
50 self.config_file = "/etc/vservers/%s.conf" % name
51 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
52 if not (os.path.isdir(self.dir) and
53 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
54 raise NoSuchVServer, "no such vserver: " + name
56 for config_file in ["/etc/vservers.conf", self.config_file]:
58 self.config.update(self.__read_config_file(config_file))
60 if ex.errno != errno.ENOENT:
62 self.remove_caps = ~vserverimpl.CAP_SAFE;
64 vm_id = int(self.config['S_CONTEXT'])
66 self.vm_running = vm_running
68 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
70 def __read_config_file(self, filename):
72 f = open(filename, "r")
76 for m in self.config_var_re.finditer(data):
77 (key, val) = m.groups()
78 config[key] = val.strip('"')
81 def __update_config_file(self, filename, newvars):
83 # read old file, apply changes
84 f = open(filename, "r")
89 for m in self.config_var_re.finditer(data):
90 (key, val) = m.groups()
91 newval = todo.pop(key, None)
93 data = data[:m.start(2)] + str(newval) + data[m.end(2):]
95 for (newkey, newval) in todo.items():
96 data += "%s=%s\n" % (newkey, newval)
103 newfile = filename + ".new"
104 f = open(newfile, "w")
108 # replace old file with new
109 os.rename(newfile, filename)
111 def __do_chroot(self):
116 def chroot_call(self, fn, *args):
118 cwd_fd = os.open(".", os.O_RDONLY)
120 root_fd = os.open("/", os.O_RDONLY)
133 def set_disklimit(self, block_limit):
135 # block_limit is in kB
137 vserverimpl.unsetdlimit(self.dir, self.ctx)
141 block_usage = vserverimpl.DLIMIT_KEEP
142 inode_usage = vserverimpl.DLIMIT_KEEP
144 # init_disk_info() must have been called to get usage values
145 block_usage = self.disk_blocks
146 inode_usage = self.disk_inodes
148 vserverimpl.setdlimit(self.dir,
153 vserverimpl.DLIMIT_INF, # inode limit
154 2) # %age reserved for root
156 def get_disklimit(self):
159 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
160 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
162 if ex.errno != errno.ESRCH:
164 # get here if no vserver disk limit has been set for xid
169 def set_sched_config(self, cpu_share, sched_flags):
171 """ Write current CPU scheduler parameters to the vserver
172 configuration file. This method does not modify the kernel CPU
173 scheduling parameters for this context. """
175 if cpu_share == int(self.config.get("CPULIMIT", -1)):
177 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
178 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
179 self.update_resources(cpu_config)
181 self.set_sched(cpu_share, sched_flags)
183 def set_sched(self, cpu_share, sched_flags = 0):
185 """ Update kernel CPU scheduling parameters for this context. """
187 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
190 # have no way of querying scheduler right now on a per vserver basis
193 def set_memlimit(self, limit):
194 ret = vserverimpl.setrlimit(self.ctx,5,limit)
197 def get_memlimit(self):
198 ret = vserverimpl.getrlimit(self.ctx,5)
201 def set_tasklimit(self, limit):
202 ret = vserverimpl.setrlimit(self.ctx,6,limit)
205 def get_tasklimit(self):
206 ret = vserverimpl.getrlimit(self.ctx,6)
209 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
210 exempt_min = None, exempt_max = None,
211 share = None, dev = "eth0"):
214 bwlimit.on(self.ctx, dev, share,
215 minrate, maxrate, exempt_min, exempt_max)
217 bwlimit.off(self.ctx, dev)
219 def get_bwlimit(self, dev = "eth0"):
221 result = bwlimit.get(self.ctx)
222 # result of bwlimit.get is (ctx, share, minrate, maxrate)
227 def open(self, filename, mode = "r", bufsize = -1):
229 return self.chroot_call(open, filename, mode, bufsize)
231 def __do_chcontext(self, state_file):
234 print >>state_file, "S_CONTEXT=%u" % self.ctx
235 print >>state_file, "S_PROFILE="
238 if vserverimpl.chcontext(self.ctx):
240 vserverimpl.setup_done(self.ctx)
242 def __prep(self, runlevel, log):
244 """ Perform all the crap that the vserver script does before
245 actually executing the startup scripts. """
247 # remove /var/run and /var/lock/subsys files
248 # but don't remove utmp from the top-level /var/run
250 LOCKDIR = "/var/lock/subsys"
251 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
252 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
253 (out + map((dir + "/").__add__, ff(files)),
255 list(os.walk(RUNDIR)),
257 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
258 os.listdir(LOCKDIR)))
262 # set the initial runlevel
263 f = open(RUNDIR + "/utmp", "w")
264 utmp.set_runlevel(f, runlevel)
267 # mount /proc and /dev/pts
268 self.__do_mount("none", "/proc", "proc")
269 # XXX - magic mount options
270 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
272 def __do_mount(self, *mount_args):
275 mountimpl.mount(*mount_args)
277 if ex.errno == errno.EBUSY:
278 # assume already mounted
284 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
286 self.__do_chcontext(state_file)
288 def start(self, wait, runlevel = 3):
290 self.vm_running = True
292 child_pid = os.fork()
299 # open state file to record vserver info
300 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
302 # use /dev/null for stdin, /var/log/boot.log for stdout/err
305 os.open("/dev/null", os.O_RDONLY)
307 log = open("/var/log/boot.log", "w", 0)
310 print >>log, ("%s: starting the virtual server %s" %
311 (time.asctime(time.gmtime()), self.name))
313 # perform pre-init cleanup
314 self.__prep(runlevel, log)
316 # execute each init script in turn
317 # XXX - we don't support all scripts that vserver script does
320 for cmd in self.INITSCRIPTS + [None]:
321 # wait for previous command to terminate, unless it
322 # is the last one and the caller has specified to wait
323 if cmd_pid and (cmd != None or wait):
325 os.waitpid(cmd_pid, 0)
327 print >>log, "error waiting for %s:" % cmd_pid
328 traceback.print_exc()
334 # fork and exec next command
338 # enter vserver context
339 self.__do_chcontext(state_file)
340 arg_subst = { 'runlevel': runlevel }
341 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
343 print >>log, "executing '%s'" % " ".join(cmd_args)
344 os.execl(cmd[0], *cmd_args)
346 traceback.print_exc()
349 # don't want to write state_file multiple times
352 # we get here due to an exception in the top-level child process
353 except Exception, ex:
354 traceback.print_exc()
360 def set_resources(self):
362 """ Called when vserver context is entered for first time,
363 should be overridden by subclass. """
367 def update_resources(self, resources):
369 self.config.update(resources)
371 # write new values to configuration file
372 self.__update_config_file(self.config_file, resources)
374 def init_disk_info(self):
376 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
380 def stop(self, signal = signal.SIGKILL):
382 vserverimpl.killall(self.ctx, signal)
383 self.vm_running = False
387 def create(vm_name, static = False, ctor = VServer):
391 options += ['--static']
392 runcmd.run('vuseradd', options + [vm_name])
393 vm_id = pwd.getpwnam(vm_name)[2]
395 return ctor(vm_name, vm_id)