X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=python%2Fvserver.py;h=c8470727a9bad865a47a18e45f976b70a8c5f3bd;hb=88e0cf0d3484efdf8d6de1a1c683ce65fe32ba83;hp=79370229af690d255b36aca288b5d04d5cf60cc6;hpb=c4ab647140e6387139cb7fd54c43e96fc6981a5c;p=util-vserver-pl.git diff --git a/python/vserver.py b/python/vserver.py index 7937022..c847072 100644 --- a/python/vserver.py +++ b/python/vserver.py @@ -12,13 +12,16 @@ import sys import time import traceback import subprocess +import commands import resource import vserverimpl -import cpulimit, bwlimit +import cpulimit +import plnode.bwlimit as bwlimit from vserverimpl import DLIMIT_INF from vserverimpl import VC_LIM_KEEP +from vserverimpl import VC_LIM_INFINITY from vserverimpl import VLIMIT_NSOCK from vserverimpl import VLIMIT_OPENFD from vserverimpl import VLIMIT_ANON @@ -41,6 +44,8 @@ RLIMITS = { "NSOCK": VLIMIT_NSOCK, "ANON": VLIMIT_ANON, "SHMEM": VLIMIT_SHMEM} +CPU_SHARE_MULT = 1024 + # add in the platform supported rlimits for entry in resource.__dict__.keys(): if entry.find("RLIMIT_")==0: @@ -52,7 +57,6 @@ for entry in resource.__dict__.keys(): class NoSuchVServer(Exception): pass - class VServerConfig: def __init__(self, name, directory): self.name = name @@ -131,19 +135,15 @@ class VServerConfig: class VServer: - INITSCRIPTS = [('/etc/rc.vinit', 'start'), - ('/etc/rc.d/rc', '%(runlevel)d')] - def __init__(self, name, vm_id = None, vm_running = None, logfile=None): self.name = name - self.rlimits_changed = False self.dir = "%s/%s" % (vserverimpl.VSERVER_BASEDIR, name) if not (os.path.isdir(self.dir) and os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)): raise NoSuchVServer, "no such vserver: " + name self.config = VServerConfig(name, "/etc/vservers/%s" % name) - self.remove_caps = ~vserverimpl.CAP_SAFE; + #self.remove_caps = ~vserverimpl.CAP_SAFE; if vm_id == None: vm_id = int(self.config.get('context')) self.ctx = vm_id @@ -153,92 +153,103 @@ class VServer: self.logfile = logfile # inspired from nodemanager's logger + def log_in_file (self, fd, msg): + if not msg: msg="\n" + if not msg.endswith('\n'): msg += '\n' + os.write(fd, '%s: %s' % (time.asctime(time.gmtime()), msg)) + def log(self,msg): if self.logfile: try: fd = os.open(self.logfile,os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600) - if not msg.endswith('\n'): msg += '\n' - os.write(fd, '%s: %s' % (time.asctime(time.gmtime()), msg)) + self.log_in_file(fd,msg) os.close(fd) except: print '%s: (%s failed to open) %s'%(time.asctime(time.gmtime()),self.logfile,msg) - def have_limits_changed(self): - return self.rlimits_changed - - def set_rlimit_limit(self,type,hard,soft,minimum): + def set_rlimit(self, type, hard, soft, min): """Generic set resource limit function for vserver""" global RLIMITS - changed = False - try: - old_hard, old_soft, old_minimum = self.get_rlimit_limit(type) - if old_hard != VC_LIM_KEEP and old_hard <> hard: changed = True - if old_soft != VC_LIM_KEEP and old_soft <> soft: changed = True - if old_minimum != VC_LIM_KEEP and old_minimum <> minimum: changed = True - self.rlimits_changed = self.rlimits_changed or changed - except OSError, e: - if self.is_running(): self.log("Unexpected error with getrlimit for running context %d" % self.ctx) + update = False - resource_type = RLIMITS[type] - try: - ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum) - except OSError, e: - if self.is_running(): self.log("Unexpected error with setrlimit for running context %d" % self.ctx) - - def set_rlimit_config(self,type,hard,soft,minimum): - """Generic set resource limit function for vserver""" if hard <> VC_LIM_KEEP: self.config.update('rlimits/%s.hard' % type.lower(), hard) + update = True if soft <> VC_LIM_KEEP: self.config.update('rlimits/%s.soft' % type.lower(), soft) - if minimum <> VC_LIM_KEEP: - self.config.update('rlimits/%s.min' % type.lower(), minimum) - self.set_rlimit_limit(type,hard,soft,minimum) + update = True + if min <> VC_LIM_KEEP: + self.config.update('rlimits/%s.min' % type.lower(), min) + update = True - def get_rlimit_limit(self,type): - """Generic get resource configuration function for vserver""" - global RLIMITS - resource_type = RLIMITS[type] - try: - ret = vserverimpl.getrlimit(self.ctx,resource_type) - except OSError, e: - self.log("Unexpected error with getrlimit for context %d" % self.ctx) - ret = self.get_rlimit_config(type) - return ret + if self.is_running() and update: + resource_type = RLIMITS[type] + try: + vserverimpl.setrlimit(self.ctx, resource_type, hard, soft, min) + except OSError, e: + self.log("Error: setrlimit(%d, %s, %d, %d, %d): %s" + % (self.ctx, type.lower(), hard, soft, min, e)) + + return update + + def get_prefix_from_capabilities(self, capabilities, prefix): + split_caps = capabilities.split(',') + return ",".join(["%s" % (c) for c in split_caps if c.startswith(prefix.upper()) or c.startswith(prefix.lower())]) - def get_rlimit_config(self,type): - """Generic get resource configuration function for vserver""" - hard = int(self.config.get("rlimits/%s.hard"%type.lower(),VC_LIM_KEEP)) - soft = int(self.config.get("rlimits/%s.soft"%type.lower(),VC_LIM_KEEP)) - minimum = int(self.config.get("rlimits/%s.min"%type.lower(),VC_LIM_KEEP)) - return (hard,soft,minimum) + def get_bcaps_from_capabilities(self, capabilities): + return self.get_prefix_from_capabilities(capabilities, "cap_") - def set_capabilities(self, capabilities): - return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities)) + def get_ccaps_from_capabilities(self, capabilities): + return self.get_prefix_from_capabilities(capabilities, "vxc_") def set_capabilities_config(self, capabilities): - self.config.update('bcapabilities', capabilities) - self.set_capabilities(capabilities) + bcaps = self.get_bcaps_from_capabilities(capabilities) + ccaps = self.get_ccaps_from_capabilities(capabilities) + if len(bcaps) > 0: + bcaps += "," + bcaps += "CAP_NET_RAW" + self.config.update('bcapabilities', bcaps) + self.config.update('ccapabilities', ccaps) + ret = vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(bcaps)) + if ret > 0: + return ret + return vserverimpl.setccaps(self.ctx, vserverimpl.text2ccaps(ccaps)) def get_capabilities(self): - return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx)) + bcaps = vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx)) + ccaps = vserverimpl.ccaps2text(vserverimpl.getccaps(self.ctx)) + if bcaps and ccaps: + ccaps = "," + ccaps + return (bcaps + ccaps) def get_capabilities_config(self): - return self.config.get('bcapabilities', '') + bcaps = self.config.get('bcapabilities', '') + ccaps = self.config.get('ccapabilities', '') + if bcaps and ccaps: + ccaps = "," + ccaps + return (bcaps + ccaps) def set_ipaddresses(self, addresses): vserverimpl.netremove(self.ctx, "all") - for a in addresses.split(","): - vserverimpl.netadd(self.ctx, a) + for ip in addresses: + vserverimpl.netadd(self.ctx, ip) + + def set_ipaddresses_config(self, addresses, add_loopback=True): + ip_addresses = addresses.split(",") + + # add looopback interface + if not ip_addresses.__contains__("127.0.0.1") and add_loopback: + ip_addresses.append("127.0.0.1") - def set_ipaddresses_config(self, addresses): i = 0 - for a in addresses.split(","): - self.config.update("interfaces/%d/ip" % i, a) + for ip in ip_addresses: + self.config.update("interfaces/%d/ip" % i, ip) + # create emtpy nodev files to silent "No device specified for" warnings + self.config.update("interfaces/%d/nodev" % i, "") i += 1 - while self.config.unset("interfaces/%d/ip" % i): + while self.config.unset("interfaces/%d/ip" % i) and self.config.update("interfaces/%d/nodev" % i, ""): i += 1 - self.set_ipaddresses(addresses) + self.set_ipaddresses(ip_addresses) def get_ipaddresses_config(self): i = 0 @@ -253,20 +264,20 @@ class VServer: def get_ipaddresses(self): # No clean way to do this right now. + self.log("Calling Vserver.get_ipaddresses for slice %s" % self.name) return None def __do_chroot(self): os.chroot(self.dir) os.chdir("/") - def chroot_call(self, fn, *args): - + def chroot_call(self, fn, *args, **kwargs): cwd_fd = os.open(".", os.O_RDONLY) try: root_fd = os.open("/", os.O_RDONLY) try: self.__do_chroot() - result = fn(*args) + result = fn(*args, **kwargs) finally: os.fchdir(root_fd) os.chroot(".") @@ -293,7 +304,6 @@ class VServer: block_usage = self.disk_blocks inode_usage = self.disk_inodes - try: vserverimpl.setdlimit(self.dir, self.ctx, @@ -305,14 +315,13 @@ class VServer: except OSError, e: self.log("Unexpected error with setdlimit for context %d" % self.ctx) - self.config.update('dlimits/0/space_total', block_limit) def is_running(self): - return vserverimpl.isrunning(self.ctx) + status = subprocess.call(["/usr/sbin/vserver", self.name, "running"], shell=False) + return not status def get_disklimit(self): - try: (self.disk_blocks, block_limit, self.disk_inodes, inode_limit, reserved) = vserverimpl.getdlimit(self.dir, self.ctx) @@ -325,26 +334,28 @@ class VServer: return block_limit def set_sched_config(self, cpu_min, cpu_share): - """ Write current CPU scheduler parameters to the vserver - configuration file. This method does not modify the kernel CPU - scheduling parameters for this context. """ - - self.config.update('sched/fill-rate', cpu_min) - self.config.update('sched/fill-rate2', cpu_share) - if cpu_share == 0: - self.config.unset('sched/idle-time') - - if self.vm_running: + configuration file. Currently, 'cpu_min' is not supported. """ + self.config.update('cgroup/cpu.shares', int(cpu_share) * CPU_SHARE_MULT) + if self.is_running(): self.set_sched(cpu_min, cpu_share) def set_sched(self, cpu_min, cpu_share): - """ Update kernel CPU scheduling parameters for this context. """ - vserverimpl.setsched(self.ctx, cpu_min, cpu_share) + """ Update kernel CPU scheduling parameters for this context. + Currently, 'cpu_min' is not supported. """ + try: + cgroup = open('/dev/cgroup/%s/cpu.shares' % self.name, 'w') + cgroup.write('%s' % (int(cpu_share) * CPU_SHARE_MULT)) + cgroup.close() + except: + pass def get_sched(self): - # have no way of querying scheduler right now on a per vserver basis - return (-1, False) + try: + cpu_share = int(int(self.config.get('cgroup/cpu.shares')) / CPU_SHARE_MULT) + except: + cpu_share = False + return (-1, cpu_share) def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None, exempt_min = None, exempt_max = None, @@ -368,115 +379,35 @@ class VServer: return self.chroot_call(open, filename, mode, bufsize) - def __do_chcontext(self, state_file): - - if state_file: - print >>state_file, "%u" % self.ctx - state_file.close() - - if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())): - self.set_resources() - vserverimpl.setup_done(self.ctx) - - def __prep(self, runlevel): - - """ Perform all the crap that the vserver script does before - actually executing the startup scripts. """ - - # remove /var/run and /var/lock/subsys files - # but don't remove utmp from the top-level /var/run - RUNDIR = "/var/run" - LOCKDIR = "/var/lock/subsys" - filter_fn = lambda fs: filter(lambda f: f != 'utmp', fs) - garbage = reduce((lambda (out, ff), (dir, subdirs, files): - (out + map((dir + "/").__add__, ff(files)), - lambda fs: fs)), - list(os.walk(RUNDIR)), - ([], filter_fn))[0] - garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__, - os.listdir(LOCKDIR))) - if False: - for f in garbage: - os.unlink(f) - - # set the initial runlevel - vserverimpl.setrunlevel(RUNDIR + "/utmp", runlevel) - - # mount /proc and /dev/pts - self.__do_mount("none", self.dir, "/proc", "proc") - # XXX - magic mount options - self.__do_mount("none", self.dir, "/dev/pts", "devpts", 0, "gid=5,mode=0620") - - def __do_mount(self, *mount_args): - - try: - vserverimpl.mount(*mount_args) - except OSError, ex: - if ex.errno == errno.EBUSY: - # assume already mounted - return - raise ex - def enter(self): - self.config.cache_it() - self.__do_chroot() - self.__do_chcontext(None) - - def start(self, wait, runlevel = 3): - self.vm_running = True - self.rlimits_changed = False - - child_pid = os.fork() - if child_pid == 0: - # child process + subprocess.call("/usr/sbin/vserver %s enter" % self.name, shell=True) + + # 2010 June 21 - Thierry + # the slice initscript now gets invoked through rc - see sliver_vs.py in nodemanager + # and, rc is triggered as part of vserver .. start + # so we don't have to worry about initscripts at all anymore here + def start(self, runlevel = 3): + if os.fork() != 0: + # Parent should just return. + self.vm_running = True + return + else: + os.setsid() + # first child process: fork again + if os.fork() != 0: + os._exit(0) # Exit parent (the first child) of the second child. + # the grandson is the working one + os.chdir('/') + os.umask(0022) try: - # get a new session - os.setsid() - - # open state file to record vserver info - state_file = open("/var/run/vservers/%s" % self.name, "w") + # start the vserver + subprocess.call(["/usr/sbin/vserver",self.name,"start"]) - # use /dev/null for stdin, /var/log/boot.log for stdout/err - fd = os.open("/dev/null", os.O_RDONLY) - if fd != 0: - os.dup2(fd, 0) - os.close(fd) - - # perform pre-init cleanup - self.__prep(runlevel) - - self.config.cache_it() - self.__do_chroot() - log = open("/var/log/boot.log", "a", 0) - if log.fileno() != 1: - os.dup2(log.fileno(), 1) - os.dup2(1, 2) - - print >>log, ("%s: starting the virtual server %s" % - (time.asctime(time.gmtime()), self.name)) - # execute each init script in turn - # XXX - we don't support all scripts that vserver script does - self.__do_chcontext(state_file) - for cmd in self.INITSCRIPTS: - try: - # enter vserver context - arg_subst = { 'runlevel': runlevel } - cmd_args = [cmd[0]] + map(lambda x: x % arg_subst, - cmd[1:]) - print >>log, "executing '%s'" % " ".join(cmd_args) - os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args) - except: - print >>log, traceback.format_exc() - os._exit(1) - - # we get here due to an exception in the top-level child process + # we get here due to an exception in the grandson process except Exception, ex: self.log(traceback.format_exc()) os._exit(0) - # parent process - return child_pid - def set_resources(self): """ Called when vserver context is entered for first time, @@ -485,6 +416,13 @@ class VServer: pass def init_disk_info(self): + try: + dlimit = vserverimpl.getdlimit(self.dir, self.ctx) + self.disk_blocks = dlimit[0] + self.disk_inodes = dlimit[2] + return self.disk_blocks * 1024 + except Exception, e: + pass cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir) p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -500,15 +438,19 @@ class VServer: (space, inodes) = line.split() self.disk_inodes = int(inodes) self.disk_blocks = int(space) - #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir) return self.disk_blocks * 1024 def stop(self, signal = signal.SIGKILL): - vserverimpl.killall(self.ctx, signal) self.vm_running = False - self.rlimits_changed = False + subprocess.call("/usr/sbin/vserver %s stop" % self.name, shell=True) + + def setname(self, slice_id): + pass + def getname(self): + '''Get vcVHI_CONTEXT field in kernel''' + return vserverimpl.getname(self.ctx) def create(vm_name, static = False, ctor = VServer):