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, 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 self.vm_running = vm_running
65 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
67 def __read_config_file(self, filename):
69 f = open(filename, "r")
73 for m in self.config_var_re.finditer(data):
74 (key, val) = m.groups()
75 config[key] = val.strip('"')
78 def __update_config_file(self, filename, newvars):
80 # read old file, apply changes
81 f = open(filename, "r")
86 for m in self.config_var_re.finditer(data):
87 (key, val) = m.groups()
88 newval = todo.pop(key, None)
90 data = data[:m.start(2)] + str(newval) + data[m.end(2):]
92 for (newkey, newval) in todo.items():
93 data += "%s=%s\n" % (newkey, newval)
100 newfile = filename + ".new"
101 f = open(newfile, "w")
105 # 'copy' original file, rename new to original
106 backup = filename + ".old"
110 if ex.errno != errno.ENOENT:
112 os.link(filename, backup)
113 os.rename(newfile, filename)
115 def __do_chroot(self):
120 def chroot_call(self, fn, *args):
122 cwd_fd = os.open(".", os.O_RDONLY)
124 root_fd = os.open("/", os.O_RDONLY)
137 def set_disklimit(self, block_limit):
139 # block_limit is in kB
141 vserverimpl.unsetdlimit(self.dir, self.ctx)
145 block_usage = vserverimpl.DLIMIT_KEEP
146 inode_usage = vserverimpl.DLIMIT_KEEP
148 # init_disk_info() must have been called to get usage values
149 block_usage = self.disk_blocks
150 inode_usage = self.disk_inodes
152 vserverimpl.setdlimit(self.dir,
157 vserverimpl.DLIMIT_INF, # inode limit
158 2) # %age reserved for root
160 def get_disklimit(self):
163 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
164 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
166 if ex.errno != errno.ESRCH:
168 # get here if no vserver disk limit has been set for xid
173 def set_sched_config(self, cpu_share, sched_flags):
175 """ Write current CPU scheduler parameters to the vserver
176 configuration file. This method does not modify the kernel CPU
177 scheduling parameters for this context. """
179 if cpu_share == int(self.config.get("CPULIMIT", -1)):
181 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
182 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
183 self.update_resources(cpu_config)
185 self.set_sched(cpu_share, sched_flags)
187 def set_sched(self, cpu_share, sched_flags = 0):
189 """ Update kernel CPU scheduling parameters for this context. """
191 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
194 # have no way of querying scheduler right now on a per vserver basis
197 def set_memlimit(self, limit):
198 ret = vserverimpl.setrlimit(self.ctx,5,limit)
201 def get_memlimit(self):
202 ret = vserverimpl.getrlimit(self.ctx,5)
205 def set_tasklimit(self, limit):
206 ret = vserverimpl.setrlimit(self.ctx,6,limit)
209 def get_tasklimit(self):
210 ret = vserverimpl.getrlimit(self.ctx,6)
213 def set_bwlimit(self, maxrate, minrate = 1, share = None, dev = "eth0"):
216 bwlimit.on(self.ctx, dev, share, minrate, maxrate)
218 bwlimit.off(self.ctx, dev)
220 def get_bwlimit(self, dev = "eth0"):
222 # result of bwlimit.get is (ctx, share, minrate, maxrate)
223 return bwlimit.get(self.ctx)[1:]
225 def open(self, filename, mode = "r", bufsize = -1):
227 return self.chroot_call(open, filename, mode, bufsize)
229 def __do_chcontext(self, state_file):
232 print >>state_file, "S_CONTEXT=%u" % self.ctx
233 print >>state_file, "S_PROFILE="
236 if vserverimpl.chcontext(self.ctx):
238 vserverimpl.setup_done(self.ctx)
240 def __prep(self, runlevel, log):
242 """ Perform all the crap that the vserver script does before
243 actually executing the startup scripts. """
245 # remove /var/run and /var/lock/subsys files
246 # but don't remove utmp from the top-level /var/run
248 LOCKDIR = "/var/lock/subsys"
249 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
250 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
251 (out + map((dir + "/").__add__, ff(files)),
253 list(os.walk(RUNDIR)),
255 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
256 os.listdir(LOCKDIR)))
260 # set the initial runlevel
261 f = open(RUNDIR + "/utmp", "w")
262 utmp.set_runlevel(f, runlevel)
265 # mount /proc and /dev/pts
266 self.__do_mount("none", "/proc", "proc")
267 # XXX - magic mount options
268 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
270 def __do_mount(self, *mount_args):
273 mountimpl.mount(*mount_args)
275 if ex.errno == errno.EBUSY:
276 # assume already mounted
282 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
284 self.__do_chcontext(state_file)
286 def start(self, wait, runlevel = 3):
288 self.vm_running = True
290 child_pid = os.fork()
297 # open state file to record vserver info
298 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
300 # use /dev/null for stdin, /var/log/boot.log for stdout/err
303 os.open("/dev/null", os.O_RDONLY)
305 log = open("/var/log/boot.log", "w", 0)
308 print >>log, ("%s: starting the virtual server %s" %
309 (time.asctime(time.gmtime()), self.name))
311 # perform pre-init cleanup
312 self.__prep(runlevel, log)
314 # execute each init script in turn
315 # XXX - we don't support all scripts that vserver script does
318 for cmd in self.INITSCRIPTS + [None]:
319 # wait for previous command to terminate, unless it
320 # is the last one and the caller has specified to wait
321 if cmd_pid and (cmd != None or wait):
323 os.waitpid(cmd_pid, 0)
325 print >>log, "error waiting for %s:" % cmd_pid
326 traceback.print_exc()
332 # fork and exec next command
336 # enter vserver context
337 self.__do_chcontext(state_file)
338 arg_subst = { 'runlevel': runlevel }
339 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
341 print >>log, "executing '%s'" % " ".join(cmd_args)
342 os.execl(cmd[0], *cmd_args)
344 traceback.print_exc()
347 # don't want to write state_file multiple times
350 # we get here due to an exception in the top-level child process
351 except Exception, ex:
352 traceback.print_exc()
358 def set_resources(self):
360 """ Called when vserver context is entered for first time,
361 should be overridden by subclass. """
365 def update_resources(self, resources):
367 self.config.update(resources)
369 # write new values to configuration file
370 self.__update_config_file(self.config_file, resources)
372 def init_disk_info(self):
374 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
378 def stop(self, signal = signal.SIGKILL):
380 vserverimpl.killall(self.ctx, signal)
381 self.vm_running = False
385 def create(vm_name, static = False, ctor = VServer):
389 options += ['--static']
390 runcmd.run('vuseradd', options + [vm_name])
391 vm_id = pwd.getpwnam(vm_name)[2]
393 return ctor(vm_name, vm_id)