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")
90 for m in self.config_var_re.finditer(data):
91 (key, val) = m.groups()
92 newval = todo.pop(key, None)
94 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
95 offset += len(str(newval)) - (m.end(2)-m.start(2))
97 for (newkey, newval) in todo.items():
98 data += "%s=%s\n" % (newkey, newval)
105 newfile = filename + ".new"
106 f = open(newfile, "w")
110 # replace old file with new
111 os.rename(newfile, filename)
113 def __do_chroot(self):
118 def chroot_call(self, fn, *args):
120 cwd_fd = os.open(".", os.O_RDONLY)
122 root_fd = os.open("/", os.O_RDONLY)
135 def set_disklimit(self, block_limit):
137 # block_limit is in kB
139 vserverimpl.unsetdlimit(self.dir, self.ctx)
143 block_usage = vserverimpl.DLIMIT_KEEP
144 inode_usage = vserverimpl.DLIMIT_KEEP
146 # init_disk_info() must have been called to get usage values
147 block_usage = self.disk_blocks
148 inode_usage = self.disk_inodes
150 vserverimpl.setdlimit(self.dir,
155 vserverimpl.DLIMIT_INF, # inode limit
156 2) # %age reserved for root
158 def get_disklimit(self):
161 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
162 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
164 if ex.errno != errno.ESRCH:
166 # get here if no vserver disk limit has been set for xid
171 def set_sched_config(self, cpu_share, sched_flags):
173 """ Write current CPU scheduler parameters to the vserver
174 configuration file. This method does not modify the kernel CPU
175 scheduling parameters for this context. """
177 if cpu_share == int(self.config.get("CPULIMIT", -1)):
179 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
180 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
181 self.update_resources(cpu_config)
183 self.set_sched(cpu_share, sched_flags)
185 def set_sched(self, cpu_share, sched_flags = 0):
187 """ Update kernel CPU scheduling parameters for this context. """
189 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
192 # have no way of querying scheduler right now on a per vserver basis
195 def set_memlimit(self, limit):
196 ret = vserverimpl.setrlimit(self.ctx,5,limit)
199 def get_memlimit(self):
200 ret = vserverimpl.getrlimit(self.ctx,5)
203 def set_tasklimit(self, limit):
204 ret = vserverimpl.setrlimit(self.ctx,6,limit)
207 def get_tasklimit(self):
208 ret = vserverimpl.getrlimit(self.ctx,6)
211 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
212 exempt_min = None, exempt_max = None,
213 share = None, dev = "eth0"):
216 bwlimit.on(self.ctx, dev, share,
217 minrate, maxrate, exempt_min, exempt_max)
219 bwlimit.off(self.ctx, dev)
221 def get_bwlimit(self, dev = "eth0"):
223 result = bwlimit.get(self.ctx)
224 # result of bwlimit.get is (ctx, share, minrate, maxrate)
229 def open(self, filename, mode = "r", bufsize = -1):
231 return self.chroot_call(open, filename, mode, bufsize)
233 def __do_chcontext(self, state_file):
236 print >>state_file, "S_CONTEXT=%u" % self.ctx
237 print >>state_file, "S_PROFILE="
240 if vserverimpl.chcontext(self.ctx):
242 vserverimpl.setup_done(self.ctx)
244 def __prep(self, runlevel, log):
246 """ Perform all the crap that the vserver script does before
247 actually executing the startup scripts. """
249 # remove /var/run and /var/lock/subsys files
250 # but don't remove utmp from the top-level /var/run
252 LOCKDIR = "/var/lock/subsys"
253 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
254 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
255 (out + map((dir + "/").__add__, ff(files)),
257 list(os.walk(RUNDIR)),
259 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
260 os.listdir(LOCKDIR)))
264 # set the initial runlevel
265 f = open(RUNDIR + "/utmp", "w")
266 utmp.set_runlevel(f, runlevel)
269 # mount /proc and /dev/pts
270 self.__do_mount("none", "/proc", "proc")
271 # XXX - magic mount options
272 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
274 def __do_mount(self, *mount_args):
277 mountimpl.mount(*mount_args)
279 if ex.errno == errno.EBUSY:
280 # assume already mounted
286 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
288 self.__do_chcontext(state_file)
290 def start(self, wait, runlevel = 3):
292 self.vm_running = True
294 child_pid = os.fork()
301 # open state file to record vserver info
302 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
304 # use /dev/null for stdin, /var/log/boot.log for stdout/err
307 os.open("/dev/null", os.O_RDONLY)
309 log = open("/var/log/boot.log", "w", 0)
312 print >>log, ("%s: starting the virtual server %s" %
313 (time.asctime(time.gmtime()), self.name))
315 # perform pre-init cleanup
316 self.__prep(runlevel, log)
318 # execute each init script in turn
319 # XXX - we don't support all scripts that vserver script does
322 for cmd in self.INITSCRIPTS + [None]:
323 # wait for previous command to terminate, unless it
324 # is the last one and the caller has specified to wait
325 if cmd_pid and (cmd != None or wait):
327 os.waitpid(cmd_pid, 0)
329 print >>log, "error waiting for %s:" % cmd_pid
330 traceback.print_exc()
336 # fork and exec next command
340 # enter vserver context
341 self.__do_chcontext(state_file)
342 arg_subst = { 'runlevel': runlevel }
343 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
345 print >>log, "executing '%s'" % " ".join(cmd_args)
346 os.execl(cmd[0], *cmd_args)
348 traceback.print_exc()
351 # don't want to write state_file multiple times
354 # we get here due to an exception in the top-level child process
355 except Exception, ex:
356 traceback.print_exc()
362 def set_resources(self):
364 """ Called when vserver context is entered for first time,
365 should be overridden by subclass. """
369 def update_resources(self, resources):
371 self.config.update(resources)
373 # write new values to configuration file
374 self.__update_config_file(self.config_file, resources)
376 def init_disk_info(self):
378 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
382 def stop(self, signal = signal.SIGKILL):
384 vserverimpl.killall(self.ctx, signal)
385 self.vm_running = False
389 def create(vm_name, static = False, ctor = VServer):
393 options += ['--static']
394 runcmd.run('vuseradd', options + [vm_name])
395 vm_id = pwd.getpwnam(vm_name)[2]
397 return ctor(vm_name, vm_id)