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 # 'copy' original file, rename new to original
109 backup = filename + ".old"
113 if ex.errno != errno.ENOENT:
115 os.link(filename, backup)
116 os.rename(newfile, filename)
118 def __do_chroot(self):
123 def chroot_call(self, fn, *args):
125 cwd_fd = os.open(".", os.O_RDONLY)
127 root_fd = os.open("/", os.O_RDONLY)
140 def set_disklimit(self, block_limit):
142 # block_limit is in kB
144 vserverimpl.unsetdlimit(self.dir, self.ctx)
148 block_usage = vserverimpl.DLIMIT_KEEP
149 inode_usage = vserverimpl.DLIMIT_KEEP
151 # init_disk_info() must have been called to get usage values
152 block_usage = self.disk_blocks
153 inode_usage = self.disk_inodes
155 vserverimpl.setdlimit(self.dir,
160 vserverimpl.DLIMIT_INF, # inode limit
161 2) # %age reserved for root
163 def get_disklimit(self):
166 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
167 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
169 if ex.errno != errno.ESRCH:
171 # get here if no vserver disk limit has been set for xid
176 def set_sched_config(self, cpu_share, sched_flags):
178 """ Write current CPU scheduler parameters to the vserver
179 configuration file. This method does not modify the kernel CPU
180 scheduling parameters for this context. """
182 if cpu_share == int(self.config.get("CPULIMIT", -1)):
184 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
185 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
186 self.update_resources(cpu_config)
188 self.set_sched(cpu_share, sched_flags)
190 def set_sched(self, cpu_share, sched_flags = 0):
192 """ Update kernel CPU scheduling parameters for this context. """
194 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
197 # have no way of querying scheduler right now on a per vserver basis
200 def set_memlimit(self, limit):
201 ret = vserverimpl.setrlimit(self.ctx,5,limit)
204 def get_memlimit(self):
205 ret = vserverimpl.getrlimit(self.ctx,5)
208 def set_tasklimit(self, limit):
209 ret = vserverimpl.setrlimit(self.ctx,6,limit)
212 def get_tasklimit(self):
213 ret = vserverimpl.getrlimit(self.ctx,6)
216 def set_bwlimit(self, maxrate, minrate = 1, share = None, dev = "eth0"):
219 bwlimit.on(self.ctx, dev, share, minrate, maxrate)
221 bwlimit.off(self.ctx, dev)
223 def get_bwlimit(self, dev = "eth0"):
225 result = bwlimit.get(self.ctx)
226 # result of bwlimit.get is (ctx, share, minrate, maxrate)
231 def open(self, filename, mode = "r", bufsize = -1):
233 return self.chroot_call(open, filename, mode, bufsize)
235 def __do_chcontext(self, state_file):
238 print >>state_file, "S_CONTEXT=%u" % self.ctx
239 print >>state_file, "S_PROFILE="
242 if vserverimpl.chcontext(self.ctx):
244 vserverimpl.setup_done(self.ctx)
246 def __prep(self, runlevel, log):
248 """ Perform all the crap that the vserver script does before
249 actually executing the startup scripts. """
251 # remove /var/run and /var/lock/subsys files
252 # but don't remove utmp from the top-level /var/run
254 LOCKDIR = "/var/lock/subsys"
255 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
256 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
257 (out + map((dir + "/").__add__, ff(files)),
259 list(os.walk(RUNDIR)),
261 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
262 os.listdir(LOCKDIR)))
266 # set the initial runlevel
267 f = open(RUNDIR + "/utmp", "w")
268 utmp.set_runlevel(f, runlevel)
271 # mount /proc and /dev/pts
272 self.__do_mount("none", "/proc", "proc")
273 # XXX - magic mount options
274 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
276 def __do_mount(self, *mount_args):
279 mountimpl.mount(*mount_args)
281 if ex.errno == errno.EBUSY:
282 # assume already mounted
288 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
290 self.__do_chcontext(state_file)
292 def start(self, wait, runlevel = 3):
294 self.vm_running = True
296 child_pid = os.fork()
303 # open state file to record vserver info
304 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
306 # use /dev/null for stdin, /var/log/boot.log for stdout/err
309 os.open("/dev/null", os.O_RDONLY)
311 log = open("/var/log/boot.log", "w", 0)
314 print >>log, ("%s: starting the virtual server %s" %
315 (time.asctime(time.gmtime()), self.name))
317 # perform pre-init cleanup
318 self.__prep(runlevel, log)
320 # execute each init script in turn
321 # XXX - we don't support all scripts that vserver script does
324 for cmd in self.INITSCRIPTS + [None]:
325 # wait for previous command to terminate, unless it
326 # is the last one and the caller has specified to wait
327 if cmd_pid and (cmd != None or wait):
329 os.waitpid(cmd_pid, 0)
331 print >>log, "error waiting for %s:" % cmd_pid
332 traceback.print_exc()
338 # fork and exec next command
342 # enter vserver context
343 self.__do_chcontext(state_file)
344 arg_subst = { 'runlevel': runlevel }
345 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
347 print >>log, "executing '%s'" % " ".join(cmd_args)
348 os.execl(cmd[0], *cmd_args)
350 traceback.print_exc()
353 # don't want to write state_file multiple times
356 # we get here due to an exception in the top-level child process
357 except Exception, ex:
358 traceback.print_exc()
364 def set_resources(self):
366 """ Called when vserver context is entered for first time,
367 should be overridden by subclass. """
371 def update_resources(self, resources):
373 self.config.update(resources)
375 # write new values to configuration file
376 self.__update_config_file(self.config_file, resources)
378 def init_disk_info(self):
380 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
384 def stop(self, signal = signal.SIGKILL):
386 vserverimpl.killall(self.ctx, signal)
387 self.vm_running = False
391 def create(vm_name, static = False, ctor = VServer):
395 options += ['--static']
396 runcmd.run('vuseradd', options + [vm_name])
397 vm_id = pwd.getpwnam(vm_name)[2]
399 return ctor(vm_name, vm_id)