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
67 self.config_file = "/etc/vservers/%s.conf" % name
68 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
69 if not (os.path.isdir(self.dir) and
70 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
71 raise NoSuchVServer, "no such vserver: " + name
73 for config_file in ["/etc/vservers.conf", self.config_file]:
75 self.config.update(self.__read_config_file(config_file))
77 if ex.errno != errno.ENOENT:
79 self.remove_caps = ~vserverimpl.CAP_SAFE;
81 vm_id = int(self.config['S_CONTEXT'])
83 self.vm_running = vm_running
85 def have_limits_changed(self):
86 return self.rlimits_changed
88 def set_rlimit_limit(self,type,hard,soft,minimum):
89 """Generic set resource limit function for vserver"""
93 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
94 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
95 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
96 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
97 self.rlimits_changed = self.rlimits_changed or changed
99 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
101 resource_type = RLIMITS[type]
103 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
105 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
107 def set_rlimit_config(self,type,hard,soft,minimum):
108 """Generic set resource limit function for vserver"""
110 if hard <> VC_LIM_KEEP:
111 resources["VS_%s_HARD"%type] = hard
112 if soft <> VC_LIM_KEEP:
113 resources["VS_%s_SOFT"%type] = soft
114 if minimum <> VC_LIM_KEEP:
115 resources["VS_%s_MINIMUM"%type] = minimum
117 self.update_resources(resources)
118 self.set_rlimit_limit(type,hard,soft,minimum)
120 def get_rlimit_limit(self,type):
121 """Generic get resource configuration function for vserver"""
123 resource_type = RLIMITS[type]
125 ret = vserverimpl.getrlimit(self.ctx,resource_type)
127 print "Unexpected error with getrlimit for context %d" % self.ctx
128 ret = self.get_rlimit_config(type)
131 def get_rlimit_config(self,type):
132 """Generic get resource configuration function for vserver"""
133 hard = int(self.config.get("VS_%s_HARD"%type,VC_LIM_KEEP))
134 soft = int(self.config.get("VS_%s_SOFT"%type,VC_LIM_KEEP))
135 minimum = int(self.config.get("VS_%s_MINIMUM"%type,VC_LIM_KEEP))
136 return (hard,soft,minimum)
138 def set_WHITELISTED_config(self,whitelisted):
139 resources = {'VS_WHITELISTED': whitelisted}
140 self.update_resources(resources)
142 config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
144 def __read_config_file(self, filename):
146 f = open(filename, "r")
150 for m in self.config_var_re.finditer(data):
151 (key, val) = m.groups()
152 config[key] = val.strip('"')
155 def __update_config_file(self, filename, newvars):
157 # read old file, apply changes
158 f = open(filename, "r")
161 todo = newvars.copy()
164 for m in self.config_var_re.finditer(data):
165 (key, val) = m.groups()
166 newval = todo.pop(key, None)
168 data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
169 offset += len(str(newval)) - (m.end(2)-m.start(2))
171 for (newkey, newval) in todo.items():
172 data += "%s=%s\n" % (newkey, newval)
179 newfile = filename + ".new"
180 f = open(newfile, "w")
184 # replace old file with new
185 os.rename(newfile, filename)
187 def __do_chroot(self):
192 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):
298 return self.chroot_call(open, filename, mode, bufsize)
300 def __do_chcontext(self, state_file):
303 print >>state_file, "S_CONTEXT=%u" % self.ctx
304 print >>state_file, "S_PROFILE="
307 if vserverimpl.chcontext(self.ctx):
309 vserverimpl.setup_done(self.ctx)
311 def __prep(self, runlevel, log):
313 """ Perform all the crap that the vserver script does before
314 actually executing the startup scripts. """
316 # remove /var/run and /var/lock/subsys files
317 # but don't remove utmp from the top-level /var/run
319 LOCKDIR = "/var/lock/subsys"
320 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
321 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
322 (out + map((dir + "/").__add__, ff(files)),
324 list(os.walk(RUNDIR)),
326 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
327 os.listdir(LOCKDIR)))
332 # set the initial runlevel
333 f = open(RUNDIR + "/utmp", "w")
334 utmp.set_runlevel(f, runlevel)
337 # mount /proc and /dev/pts
338 self.__do_mount("none", "/proc", "proc")
339 # XXX - magic mount options
340 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
342 def __do_mount(self, *mount_args):
345 mountimpl.mount(*mount_args)
347 if ex.errno == errno.EBUSY:
348 # assume already mounted
353 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
355 self.__do_chcontext(state_file)
357 def start(self, wait, runlevel = 3):
358 self.vm_running = True
359 self.rlimits_changed = False
361 child_pid = os.fork()
368 # open state file to record vserver info
369 state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
371 # use /dev/null for stdin, /var/log/boot.log for stdout/err
374 os.open("/dev/null", os.O_RDONLY)
376 log = open("/var/log/boot.log", "w", 0)
379 print >>log, ("%s: starting the virtual server %s" %
380 (time.asctime(time.gmtime()), self.name))
382 # perform pre-init cleanup
383 self.__prep(runlevel, log)
385 # execute each init script in turn
386 # XXX - we don't support all scripts that vserver script does
387 self.__do_chcontext(state_file)
388 for cmd in self.INITSCRIPTS:
391 # enter vserver context
392 arg_subst = { 'runlevel': runlevel }
393 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
395 print >>log, "executing '%s'" % " ".join(cmd_args)
396 os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args)
398 traceback.print_exc()
401 # we get here due to an exception in the top-level child process
402 except Exception, ex:
403 traceback.print_exc()
409 def set_resources(self):
411 """ Called when vserver context is entered for first time,
412 should be overridden by subclass. """
416 def update_resources(self, resources):
417 self.config.update(resources)
419 # write new values to configuration file
420 self.__update_config_file(self.config_file, resources)
422 def init_disk_info(self):
423 (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
426 def stop(self, signal = signal.SIGKILL):
427 vserverimpl.killall(self.ctx, signal)
428 self.vm_running = False
429 self.rlimits_changed = False
432 def create(vm_name, static = False, ctor = VServer):
436 options += ['--static']
437 runcmd.run('vuseradd', options + [vm_name])
438 vm_id = pwd.getpwnam(vm_name)[2]
440 return ctor(vm_name, vm_id)