1 # Copyright 2005 Princeton University
3 #$Id: vserver.py,v 1.67 2007/07/31 18:14:02 dhozac Exp $
21 import cpulimit, bwlimit
23 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
24 from vserverimpl import DLIMIT_INF
25 from vserverimpl import VC_LIM_KEEP
26 from vserverimpl import VLIMIT_NSOCK
27 from vserverimpl import VLIMIT_OPENFD
28 from vserverimpl import VLIMIT_ANON
29 from vserverimpl import VLIMIT_SHMEM
32 # these are the flags taken from the kernel linux/vserver/legacy.h
35 FLAGS_SCHED = 2 # XXX - defined in util-vserver/src/chcontext.c
43 RLIMITS = { "NSOCK": VLIMIT_NSOCK,
44 "OPENFD": VLIMIT_OPENFD,
46 "SHMEM": VLIMIT_SHMEM}
48 # add in the platform supported rlimits
49 for entry in resource.__dict__.keys():
50 if entry.find("RLIMIT_")==0:
51 k = entry[len("RLIMIT_"):]
52 if not RLIMITS.has_key(k):
53 RLIMITS[k]=resource.__dict__[entry]
55 print "WARNING: duplicate RLIMITS key %s" % k
57 class NoSuchVServer(Exception): pass
61 def __init__(self, name, directory):
65 def get(self, option, default = None):
67 f = open(os.path.join(self.dir, option), "r")
68 buf = f.readline().rstrip()
72 if default is not None:
75 raise KeyError, "Key %s is not set for %s" % (option, self.name)
77 def update(self, option, value):
79 old_umask = os.umask(0022)
80 filename = os.path.join(self.dir, option)
82 os.makedirs(os.path.dirname(filename), 0755)
85 f = open(filename, 'w')
86 if isinstance(value, list):
87 f.write("%s\n" % "\n".join(value))
89 f.write("%s\n" % value)
95 def unset(self, option):
97 filename = os.path.join(self.dir, option)
99 os.removedirs(os.path.dirname(filename))
107 INITSCRIPTS = [('/etc/rc.vinit', 'start'),
108 ('/etc/rc.d/rc', '%(runlevel)d')]
110 def __init__(self, name, vm_id = None, vm_running = None):
113 self.rlimits_changed = False
114 self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name)
115 if not (os.path.isdir(self.dir) and
116 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
117 raise NoSuchVServer, "no such vserver: " + name
118 self.config = VServerConfig(name, "/etc/vservers/%s" % name)
119 self.remove_caps = ~vserverimpl.CAP_SAFE;
121 vm_id = int(self.config.get('context'))
123 if vm_running == None:
124 vm_running = self.is_running()
125 self.vm_running = vm_running
127 def have_limits_changed(self):
128 return self.rlimits_changed
130 def set_rlimit_limit(self,type,hard,soft,minimum):
131 """Generic set resource limit function for vserver"""
135 old_hard, old_soft, old_minimum = self.get_rlimit_limit(type)
136 if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True
137 if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True
138 if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True
139 self.rlimits_changed = self.rlimits_changed or changed
141 if self.is_running(): print "Unexpected error with getrlimit for running context %d" % self.ctx
143 resource_type = RLIMITS[type]
145 ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
147 if self.is_running(): print "Unexpected error with setrlimit for running context %d" % self.ctx
149 def set_rlimit_config(self,type,hard,soft,minimum):
150 """Generic set resource limit function for vserver"""
151 if hard <> VC_LIM_KEEP:
152 self.config.update('rlimits/%s.hard' % type.lower(), hard)
153 if soft <> VC_LIM_KEEP:
154 self.config.update('rlimits/%s.soft' % type.lower(), soft)
155 if minimum <> VC_LIM_KEEP:
156 self.config.update('rlimits/%s.min' % type.lower(), minimum)
157 self.set_rlimit_limit(type,hard,soft,minimum)
159 def get_rlimit_limit(self,type):
160 """Generic get resource configuration function for vserver"""
162 resource_type = RLIMITS[type]
164 ret = vserverimpl.getrlimit(self.ctx,resource_type)
166 print "Unexpected error with getrlimit for context %d" % self.ctx
167 ret = self.get_rlimit_config(type)
170 def get_rlimit_config(self,type):
171 """Generic get resource configuration function for vserver"""
172 hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP))
173 soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP))
174 minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP))
175 return (hard,soft,minimum)
177 def set_WHITELISTED_config(self,whitelisted):
178 self.config.update('whitelisted', whitelisted)
180 def set_capabilities(self, capabilities):
181 return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
183 def set_capabilities_config(self, capabilities):
184 self.config.update('bcapabilities', capabilities)
185 self.set_capabilities(capabilities)
187 def get_capabilities(self):
188 return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
190 def get_capabilities_config(self):
191 return self.config.get('bcapabilities', '')
193 def set_ipaddresses(self, addresses):
194 vserverimpl.netremove(self.ctx, "all")
195 for a in addresses.split(","):
196 vserverimpl.netadd(self.ctx, a)
198 def set_ipaddresses_config(self, addresses):
200 for a in addresses.split(","):
201 self.config.update("interfaces/%d/ip" % i, a)
203 while self.config.unset("interfaces/%d/ip" % i):
205 self.set_ipaddresses(addresses)
207 def get_ipaddresses_config(self):
211 r = self.config.get("interfaces/%d/ip" % i, '')
218 def get_ipaddresses(self):
219 # No clean way to do this right now.
222 def __do_chroot(self):
227 def chroot_call(self, fn, *args):
229 cwd_fd = os.open(".", os.O_RDONLY)
231 root_fd = os.open("/", os.O_RDONLY)
244 def set_disklimit(self, block_limit):
245 # block_limit is in kB
248 vserverimpl.unsetdlimit(self.dir, self.ctx)
250 print "Unexpected error with unsetdlimit for context %d" % self.ctx
254 block_usage = vserverimpl.DLIMIT_KEEP
255 inode_usage = vserverimpl.DLIMIT_KEEP
257 # init_disk_info() must have been called to get usage values
258 block_usage = self.disk_blocks
259 inode_usage = self.disk_inodes
263 vserverimpl.setdlimit(self.dir,
268 vserverimpl.DLIMIT_INF, # inode limit
269 2) # %age reserved for root
271 print "Unexpected error with setdlimit for context %d" % self.ctx
274 self.config.update('dlimits/0/space_total', block_limit)
276 def is_running(self):
277 return vserverimpl.isrunning(self.ctx)
279 def get_disklimit(self):
282 (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
283 reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
285 if ex.errno != errno.ESRCH:
287 # get here if no vserver disk limit has been set for xid
292 def set_sched_config(self, cpu_share, sched_flags):
294 """ Write current CPU scheduler parameters to the vserver
295 configuration file. This method does not modify the kernel CPU
296 scheduling parameters for this context. """
298 if sched_flags & SCHED_CPU_GUARANTEED:
299 cpu_guaranteed = cpu_share
302 self.config.update('sched/fill-rate2', cpu_share)
303 self.config.update('sched/fill-rate', cpu_guaranteed)
306 self.set_sched(cpu_share, sched_flags)
308 def set_sched(self, cpu_share, sched_flags = 0):
309 """ Update kernel CPU scheduling parameters for this context. """
310 vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
313 # have no way of querying scheduler right now on a per vserver basis
316 def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
317 exempt_min = None, exempt_max = None,
318 share = None, dev = "eth0"):
321 bwlimit.off(self.ctx, dev)
323 bwlimit.on(self.ctx, dev, share,
324 minrate, maxrate, exempt_min, exempt_max)
326 def get_bwlimit(self, dev = "eth0"):
328 result = bwlimit.get(self.ctx)
329 # result of bwlimit.get is (ctx, share, minrate, maxrate)
334 def open(self, filename, mode = "r", bufsize = -1):
336 return self.chroot_call(open, filename, mode, bufsize)
338 def __do_chcontext(self, state_file):
341 print >>state_file, "%u" % self.ctx
344 if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
346 vserverimpl.setup_done(self.ctx)
348 def __prep(self, runlevel, log):
350 """ Perform all the crap that the vserver script does before
351 actually executing the startup scripts. """
353 # remove /var/run and /var/lock/subsys files
354 # but don't remove utmp from the top-level /var/run
356 LOCKDIR = "/var/lock/subsys"
357 filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs)
358 garbage = reduce((lambda (out, ff), (dir, subdirs, files):
359 (out + map((dir + "/").__add__, ff(files)),
361 list(os.walk(RUNDIR)),
363 garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
364 os.listdir(LOCKDIR)))
369 # set the initial runlevel
370 f = open(RUNDIR + "/utmp", "w")
371 utmp.set_runlevel(f, runlevel)
374 # mount /proc and /dev/pts
375 self.__do_mount("none", "/proc", "proc")
376 # XXX - magic mount options
377 self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
379 def __do_mount(self, *mount_args):
382 mountimpl.mount(*mount_args)
384 if ex.errno == errno.EBUSY:
385 # assume already mounted
391 self.__do_chcontext(None)
393 def start(self, wait, runlevel = 3):
394 self.vm_running = True
395 self.rlimits_changed = False
397 child_pid = os.fork()
404 # open state file to record vserver info
405 state_file = open("/var/run/vservers/%s" % self.name, "w")
407 # use /dev/null for stdin, /var/log/boot.log for stdout/err
408 fd = os.open("/dev/null", os.O_RDONLY)
413 log = open("/var/log/boot.log", "w", 0)
414 if log.fileno() != 1:
415 os.dup2(log.fileno(), 1)
418 print >>log, ("%s: starting the virtual server %s" %
419 (time.asctime(time.gmtime()), self.name))
421 # perform pre-init cleanup
422 self.__prep(runlevel, log)
424 # execute each init script in turn
425 # XXX - we don't support all scripts that vserver script does
426 self.__do_chcontext(state_file)
427 for cmd in self.INITSCRIPTS + [None]:
429 # enter vserver context
430 arg_subst = { 'runlevel': runlevel }
431 cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
433 print >>log, "executing '%s'" % " ".join(cmd_args)
434 os.spawnvp(os.P_WAIT,cmd[0],cmd_args)
436 traceback.print_exc()
439 # we get here due to an exception in the top-level child process
440 except Exception, ex:
441 traceback.print_exc()
447 def set_resources(self):
449 """ Called when vserver context is entered for first time,
450 should be overridden by subclass. """
454 def init_disk_info(self):
455 cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
456 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
457 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
460 line = p.stdout.readline()
462 sys.stderr.write(p.stderr.read())
467 (space, inodes) = line.split()
468 self.disk_inodes = int(inodes)
469 self.disk_blocks = int(space)
470 #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
472 return self.disk_blocks * 1024
474 def stop(self, signal = signal.SIGKILL):
475 vserverimpl.killall(self.ctx, signal)
476 self.vm_running = False
477 self.rlimits_changed = False
481 def create(vm_name, static = False, ctor = VServer):
485 options += ['--static']
486 runcmd.run('vuseradd', options + [vm_name])
487 vm_id = pwd.getpwnam(vm_name)[2]
489 return ctor(vm_name, vm_id)