1 # Copyright 2005 Princeton University
17 import vserverimpl, vduimpl
18 import cpulimit, bwlimit
20 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
21 from vserverimpl import DLIMIT_INF
22 from vserverimpl import VC_LIM_KEEP
23 from vserverimpl import VLIMIT_NSOCK
24 from vserverimpl import VLIMIT_OPENFD
25 from vserverimpl import VLIMIT_ANON
26 from vserverimpl import VLIMIT_SHMEM
29 # these are the flags taken from the kernel linux/vserver/legacy.h
32 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
40 RLIMITS = { "NSOCK": VLIMIT_NSOCK,
41 "OPENFD": VLIMIT_OPENFD,
43 "SHMEM": VLIMIT_SHMEM}
45 # add in the platform supported rlimits
46 for entry in resource.__dict__.keys():
47 if entry.find("RLIMIT_")==0:
48 k = entry[len("RLIMIT_"):]
49 if not RLIMITS.has_key(k):
50 RLIMITS[k]=resource.__dict__[entry]
52 print "WARNING: duplicate RLIMITS key %s" % k
54 class NoSuchVServer(Exception): pass
59 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
60 ('/etc/rc.d/rc', '%(runlevel)d')]
62 def __init__(self, name, vm_id = None, vm_running = False):
65 self.rlimits_changed = False
66 self.config_file = "/etc/vservers/%s.conf" % name
67 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
68 if not (os.path.isdir(self.dir) and
69 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
70 raise NoSuchVServer, "no such vserver: " + name
72 for config_file in ["/etc/vservers.conf", self.config_file]:
74 self.config.update(self.__read_config_file(config_file))
76 if ex.errno != errno.ENOENT:
78 self.remove_caps = ~vserverimpl.CAP_SAFE;
80 vm_id = int(self.config['S_CONTEXT'])
82 self.vm_running = vm_running
84 def have_limits_changed(self):
85 return self.rlimits_changed
87 def set_rlimit_limit(self,type,hard,soft,minimum):
88 """Generic set resource limit function for vserver"""
92 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
93 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
94 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
95 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
96 self.rlimits_changed = self.rlimits_changed or changed
98 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
100 resource_type = RLIMITS[type]
102 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
104 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
106 def set_rlimit_config(self,type,hard,soft,minimum):
107 """Generic set resource limit function for vserver"""
109 if hard <> VC_LIM_KEEP:
110 resources["VS_%s_HARD"%type] = hard
111 if soft <> VC_LIM_KEEP:
112 resources["VS_%s_SOFT"%type] = soft
113 if minimum <> VC_LIM_KEEP:
114 resources["VS_%s_MINIMUM"%type] = minimum
116 self.update_resources(resources)
117 self.set_rlimit_limit(type,hard,soft,minimum)
119 def get_rlimit_limit(self,type):
120 """Generic get resource configuration function for vserver"""
122 resource_type = RLIMITS[type]
124 ret = vserverimpl.getrlimit(self.ctx,resource_type)
126 print "Unexpected error with getrlimit for context %d" % self.ctx
127 ret = self.get_rlimit_config(type)
130 def get_rlimit_config(self,type):
131 """Generic get resource configuration function for vserver"""
132 hard = int(self.config.get("VS_%s_HARD"%type,VC_LIM_KEEP))
133 soft = int(self.config.get("VS_%s_SOFT"%type,VC_LIM_KEEP))
134 minimum = int(self.config.get("VS_%s_MINIMUM"%type,VC_LIM_KEEP))
135 return (hard,soft,minimum)
137 def set_WHITELISTED_config(self,whitelisted):
138 resources = {'VS_WHITELISTED': whitelisted}
139 self.update_resources(resources)
141 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
143 def __read_config_file(self, filename):
145 f = open(filename, "r")
149 for m in self.config_var_re.finditer(data):
150 (key, val) = m.groups()
151 config[key] = val.strip('"')
154 def __update_config_file(self, filename, newvars):
156 # read old file, apply changes
157 f = open(filename, "r")
160 todo = newvars.copy()
163 for m in self.config_var_re.finditer(data):
164 (key, val) = m.groups()
165 newval = todo.pop(key, None)
167 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
168 offset += len(str(newval)) - (m.end(2)-m.start(2))
170 for (newkey, newval) in todo.items():
171 data += "%s=%s\n" % (newkey, newval)
178 newfile = filename + ".new"
179 f = open(newfile, "w")
183 # replace old file with new
184 os.rename(newfile, filename)
186 def __do_chroot(self):
191 def chroot_call(self, fn, *args):
193 cwd_fd = os.open(".", os.O_RDONLY)
195 root_fd = os.open("/", os.O_RDONLY)
208 def set_disklimit(self, block_limit):
209 # block_limit is in kB
212 vserverimpl.unsetdlimit(self.dir, self.ctx)
214 print "Unexpected error with unsetdlimit for context %d" % self.ctx
218 block_usage = vserverimpl.DLIMIT_KEEP
219 inode_usage = vserverimpl.DLIMIT_KEEP
221 # init_disk_info() must have been called to get usage values
222 block_usage = self.disk_blocks
223 inode_usage = self.disk_inodes
227 vserverimpl.setdlimit(self.dir,
232 vserverimpl.DLIMIT_INF, # inode limit
233 2) # %age reserved for root
235 print "Unexpected error with setdlimit for context %d" % self.ctx
238 resources = {'VS_DISK_MAX': block_limit}
239 self.update_resources(resources)
241 def is_running(self):
242 return vserverimpl.isrunning(self.ctx)
244 def get_disklimit(self):
247 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
248 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
250 if ex.errno != errno.ESRCH:
252 # get here if no vserver disk limit has been set for xid
257 def set_sched_config(self, cpu_share, sched_flags):
259 """ Write current CPU scheduler parameters to the vserver
260 configuration file. This method does not modify the kernel CPU
261 scheduling parameters for this context. """
263 if cpu_share == int(self.config.get("CPULIMIT", -1)):
265 cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
266 cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
267 self.update_resources(cpu_config)
269 self.set_sched(cpu_share, sched_flags)
271 def set_sched(self, cpu_share, sched_flags = 0):
272 """ Update kernel CPU scheduling parameters for this context. """
273 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
276 # have no way of querying scheduler right now on a per vserver basis
279 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
280 exempt_min = None, exempt_max = None,
281 share = None, dev = "eth0"):
284 bwlimit.off(self.ctx, dev)
286 bwlimit.on(self.ctx, dev, share,
287 minrate, maxrate, exempt_min, exempt_max)
289 def get_bwlimit(self, dev = "eth0"):
291 result = bwlimit.get(self.ctx)
292 # result of bwlimit.get is (ctx, share, minrate, maxrate)
297 def open(self, filename, mode = "r", bufsize = -1):
299 return self.chroot_call(open, filename, mode, bufsize)
301 def __do_chcontext(self, state_file):
304 print >>state_file, "S_CONTEXT=%u" % self.ctx
305 print >>state_file, "S_PROFILE="
308 if vserverimpl.chcontext(self.ctx):
310 vserverimpl.setup_done(self.ctx)
312 def __prep(self, runlevel, log):
314 """ Perform all the crap that the vserver script does before
315 actually executing the startup scripts. """
317 # remove /var/run and /var/lock/subsys files
318 # but don't remove utmp from the top-level /var/run
320 LOCKDIR = "/var/lock/subsys"
321 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
322 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
323 (out + map((dir + "/").__add__, ff(files)),
325 list(os.walk(RUNDIR)),
327 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
328 os.listdir(LOCKDIR)))
333 # set the initial runlevel
334 f = open(RUNDIR + "/utmp", "w")
335 utmp.set_runlevel(f, runlevel)
338 # mount /proc and /dev/pts
339 self.__do_mount("none", "/proc", "proc")
340 # XXX - magic mount options
341 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
343 def __do_mount(self, *mount_args):
346 mountimpl.mount(*mount_args)
348 if ex.errno == errno.EBUSY:
349 # assume already mounted
355 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
357 self.__do_chcontext(state_file)
359 def start(self, wait, runlevel = 3):
360 self.vm_running = True
361 self.rlimits_changed = False
363 child_pid = os.fork()
370 # open state file to record vserver info
371 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
373 # use /dev/null for stdin, /var/log/boot.log for stdout/err
376 os.open("/dev/null", os.O_RDONLY)
378 log = open("/var/log/boot.log", "w", 0)
381 print >>log, ("%s: starting the virtual server %s" %
382 (time.asctime(time.gmtime()), self.name))
384 # perform pre-init cleanup
385 self.__prep(runlevel, log)
387 # execute each init script in turn
388 # XXX - we don't support all scripts that vserver script does
389 self.__do_chcontext(state_file)
390 for cmd in self.INITSCRIPTS + [None]:
392 # enter vserver context
393 arg_subst = { 'runlevel': runlevel }
394 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
396 print >>log, "executing '%s'" % " ".join(cmd_args)
397 os.spawnvp(os.P_WAIT,cmd[0],*cmd_args)
399 traceback.print_exc()
402 # we get here due to an exception in the top-level child process
403 except Exception, ex:
404 traceback.print_exc()
410 def set_resources(self):
412 """ Called when vserver context is entered for first time,
413 should be overridden by subclass. """
417 def update_resources(self, resources):
419 self.config.update(resources)
421 # write new values to configuration file
422 self.__update_config_file(self.config_file, resources)
424 def init_disk_info(self):
426 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
430 def stop(self, signal = signal.SIGKILL):
431 vserverimpl.killall(self.ctx, signal)
432 self.vm_running = False
433 self.rlimits_changed = False
437 def create(vm_name, static = False, ctor = VServer):
441 options += ['--static']
442 runcmd.run('vuseradd', options + [vm_name])
443 vm_id = pwd.getpwnam(vm_name)[2]
445 return ctor(vm_name, vm_id)