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
24 # these are the flags taken from the kernel linux/vserver/legacy.h
27 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
37 class NoSuchVServer(Exception): pass
43 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
44 ('/etc/rc.d/rc', '%(runlevel)d')]
46 def __init__(self, name, vm_id = None, vm_running = False):
49 self.config_file = "/etc/vservers/%s.conf" % name
50 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
51 if not (os.path.isdir(self.dir) and
52 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
53 raise NoSuchVServer, "no such vserver: " + name
55 for config_file in ["/etc/vservers.conf", self.config_file]:
57 self.config.update(self.__read_config_file(config_file))
59 if ex.errno != errno.ENOENT:
61 self.remove_caps = ~vserverimpl.CAP_SAFE;
63 vm_id = int(self.config['S_CONTEXT'])
65 self.vm_running = vm_running
67 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
69 def __read_config_file(self, filename):
71 f = open(filename, "r")
75 for m in self.config_var_re.finditer(data):
76 (key, val) = m.groups()
77 config[key] = val.strip('"')
80 def __update_config_file(self, filename, newvars):
82 # read old file, apply changes
83 f = open(filename, "r")
88 for m in self.config_var_re.finditer(data):
89 (key, val) = m.groups()
90 newval = todo.pop(key, None)
92 data = data[:m.start(2)] + str(newval) + data[m.end(2):]
94 for (newkey, newval) in todo.items():
95 data += "%s=%s\n" % (newkey, newval)
102 newfile = filename + ".new"
103 f = open(newfile, "w")
107 # 'copy' original file, rename new to original
108 backup = filename + ".old"
112 if ex.errno != errno.ENOENT:
114 os.link(filename, backup)
115 os.rename(newfile, filename)
117 def __do_chroot(self):
122 def chroot_call(self, fn, *args):
124 cwd_fd = os.open(".", os.O_RDONLY)
126 root_fd = os.open("/", os.O_RDONLY)
139 def set_disklimit(self, block_limit):
141 # block_limit is in kB
143 vserverimpl.unsetdlimit(self.dir, self.ctx)
147 block_usage = vserverimpl.DLIMIT_KEEP
148 inode_usage = vserverimpl.DLIMIT_KEEP
150 # init_disk_info() must have been called to get usage values
151 block_usage = self.disk_blocks
152 inode_usage = self.disk_inodes
154 vserverimpl.setdlimit(self.dir,
159 vserverimpl.DLIMIT_INF, # inode limit
160 2) # %age reserved for root
162 def get_disklimit(self):
165 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
166 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
168 if ex.errno != errno.ESRCH:
170 # get here if no vserver disk limit has been set for xid
175 def set_sched_config(self, cpu_share, sched_flags):
177 """ Write current CPU scheduler parameters to the vserver
178 configuration file. This method does not modify the kernel CPU
179 scheduling parameters for this context. """
181 if cpu_share == int(self.config.get("CPULIMIT", -1)):
183 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
184 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
185 self.update_resources(cpu_config)
187 self.set_sched(cpu_share, sched_flags)
189 def set_sched(self, cpu_share, sched_flags = 0):
191 """ Update kernel CPU scheduling parameters for this context. """
193 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
196 # have no way of querying scheduler right now on a per vserver basis
199 def set_memlimit(self, limit):
200 ret = vserverimpl.setrlimit(self.ctx,5,limit)
203 def get_memlimit(self):
204 ret = vserverimpl.getrlimit(self.ctx,5)
207 def set_tasklimit(self, limit):
208 ret = vserverimpl.setrlimit(self.ctx,6,limit)
211 def get_tasklimit(self):
212 ret = vserverimpl.getrlimit(self.ctx,6)
215 def set_bwlimit(self, maxrate, minrate = 1, share = None, dev = "eth0"):
218 bwlimit.on(self.ctx, dev, share, minrate, maxrate)
220 bwlimit.off(self.ctx, dev)
222 def get_bwlimit(self, dev = "eth0"):
224 # result of bwlimit.get is (ctx, share, minrate, maxrate)
225 return bwlimit.get(self.ctx)[1:]
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)