add support for bcapabilities
[util-vserver.git] / python / vserver.py
index 7e78134..5344cca 100644 (file)
@@ -13,7 +13,7 @@ import traceback
 import mountimpl
 import runcmd
 import utmp
 import mountimpl
 import runcmd
 import utmp
-import vserverimpl, vduimpl
+import vserverimpl
 import cpulimit, bwlimit
 
 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
 import cpulimit, bwlimit
 
 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
@@ -63,31 +63,65 @@ RLIMITS = {"CPU": RLIMIT_CPU,
 class NoSuchVServer(Exception): pass
 
 
 class NoSuchVServer(Exception): pass
 
 
+class VServerConfig:
+    def __init__(self, name, directory):
+        self.name = name
+        self.dir = directory
+
+    def get(self, option, default = None):
+        try:
+            f = open(os.path.join(self.dir, option), "r")
+            buf = f.readline().rstrip()
+            f.close()
+            return buf
+        except KeyError, e:
+            # No mapping exists for this option
+            raise e
+        except IOError, e:
+            if default is not None:
+                return default
+            else:
+                raise KeyError, "Key %s is not set for %s" % (option, self.name)
+
+    def update(self, option, value):
+        try:
+            old_umask = os.umask(0022)
+            filename = os.path.join(self.dir, option)
+            try:
+                os.makedirs(os.path.dirname(filename), 0755)
+            except:
+                pass
+            f = open(filename, 'w')
+            if isinstance(value, list):
+                f.write("%s\n" % "\n".join(value))
+            else:
+                f.write("%s\n" % value)
+            f.close()
+            os.umask(old_umask)
+        except KeyError, e:
+            raise KeyError, "Don't know how to handle %s, sorry" % option
+
+
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
                    ('/etc/rc.d/rc', '%(runlevel)d')]
 
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
                    ('/etc/rc.d/rc', '%(runlevel)d')]
 
-    def __init__(self, name, vm_id = None, vm_running = False):
+    def __init__(self, name, vm_id = None, vm_running = None):
 
         self.name = name
         self.rlimits_changed = False
 
         self.name = name
         self.rlimits_changed = False
-        self.config_file = "/etc/vservers/%s.conf" % name
         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.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 = {}
-        for config_file in ["/etc/vservers.conf", self.config_file]:
-            try:
-                self.config.update(self.__read_config_file(config_file))
-            except IOError, ex:
-                if ex.errno != errno.ENOENT:
-                    raise
+        self.config = VServerConfig(name, "/etc/vservers/%s" % name)
         self.remove_caps = ~vserverimpl.CAP_SAFE;
         if vm_id == None:
         self.remove_caps = ~vserverimpl.CAP_SAFE;
         if vm_id == None:
-            vm_id = int(self.config['S_CONTEXT'])
+            vm_id = int(self.config.get('context'))
         self.ctx = vm_id
         self.ctx = vm_id
+        if vm_running == None:
+            vm_running = self.is_running()
         self.vm_running = vm_running
 
     def have_limits_changed(self):
         self.vm_running = vm_running
 
     def have_limits_changed(self):
@@ -114,15 +148,12 @@ class VServer:
 
     def set_rlimit_config(self,type,hard,soft,minimum):
         """Generic set resource limit function for vserver"""
 
     def set_rlimit_config(self,type,hard,soft,minimum):
         """Generic set resource limit function for vserver"""
-        resources = {}
         if hard <> VC_LIM_KEEP:
         if hard <> VC_LIM_KEEP:
-            resources["VS_%s_HARD"%type] = hard
+            self.config.update('rlimits/%s.hard' % type.lower(), hard)
         if soft <> VC_LIM_KEEP:
         if soft <> VC_LIM_KEEP:
-            resources["VS_%s_SOFT"%type] = soft
+            self.config.update('rlimits/%s.soft' % type.lower(), soft)
         if minimum <> VC_LIM_KEEP:
         if minimum <> VC_LIM_KEEP:
-            resources["VS_%s_MINIMUM"%type] = minimum
-        if len(resources)>0:
-            self.update_resources(resources)
+            self.config.update('rlimits/%s.min' % type.lower(), minimum)
         self.set_rlimit_limit(type,hard,soft,minimum)
 
     def get_rlimit_limit(self,type):
         self.set_rlimit_limit(type,hard,soft,minimum)
 
     def get_rlimit_limit(self,type):
@@ -138,59 +169,26 @@ class VServer:
 
     def get_rlimit_config(self,type):
         """Generic get resource configuration function for vserver"""
 
     def get_rlimit_config(self,type):
         """Generic get resource configuration function for vserver"""
-        hard = int(self.config.get("VS_%s_HARD"%type,VC_LIM_KEEP))
-        soft = int(self.config.get("VS_%s_SOFT"%type,VC_LIM_KEEP))
-        minimum = int(self.config.get("VS_%s_MINIMUM"%type,VC_LIM_KEEP))
+        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 set_WHITELISTED_config(self,whitelisted):
         return (hard,soft,minimum)
 
     def set_WHITELISTED_config(self,whitelisted):
-        resources = {'VS_WHITELISTED': whitelisted}
-        self.update_resources(resources)
-
-    config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
-
-    def __read_config_file(self, filename):
-
-        f = open(filename, "r")
-        data = f.read()
-        f.close()
-        config = {}
-        for m in self.config_var_re.finditer(data):
-            (key, val) = m.groups()
-            config[key] = val.strip('"')
-        return config
-
-    def __update_config_file(self, filename, newvars):
+        self.config.update('whitelisted', whitelisted)
 
 
-        # read old file, apply changes
-        f = open(filename, "r")
-        data = f.read()
-        f.close()
-        todo = newvars.copy()
-        changed = False
-        offset = 0
-        for m in self.config_var_re.finditer(data):
-            (key, val) = m.groups()
-            newval = todo.pop(key, None)
-            if newval != None:
-                data = data[:offset+m.start(2)] + str(newval) + data[offset+m.end(2):]
-                offset += len(str(newval)) - (m.end(2)-m.start(2))
-                changed = True
-        for (newkey, newval) in todo.items():
-            data += "%s=%s\n" % (newkey, newval)
-            changed = True
-
-        if not changed:
-            return
+    def set_capabilities(self, capabilities):
+        return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
 
 
-        # write new file
-        newfile = filename + ".new"
-        f = open(newfile, "w")
-        f.write(data)
-        f.close()
+    def set_capabilities_config(self, capabilities):
+        self.config.update('bcapabilities', capabilities)
+       self.set_capabilities(capabilities)
 
 
-        # replace old file with new
-        os.rename(newfile, filename)
+    def get_capabilities(self):
+        return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
+    def get_capabilities_config(self):
+        return self.config.get('bcapabilities')
 
     def __do_chroot(self):
 
 
     def __do_chroot(self):
 
@@ -244,8 +242,7 @@ class VServer:
             print "Unexpected error with setdlimit for context %d" % self.ctx
 
 
             print "Unexpected error with setdlimit for context %d" % self.ctx
 
 
-        resources = {'VS_DISK_MAX': block_limit}
-        self.update_resources(resources)
+        self.config.update('dlimits/0/space_total', block_limit)
 
     def is_running(self):
         return vserverimpl.isrunning(self.ctx)
 
     def is_running(self):
         return vserverimpl.isrunning(self.ctx)
@@ -269,11 +266,13 @@ class VServer:
         configuration file. This method does not modify the kernel CPU
         scheduling parameters for this context. """
 
         configuration file. This method does not modify the kernel CPU
         scheduling parameters for this context. """
 
-        if cpu_share == int(self.config.get("CPULIMIT", -1)):
-            return
-        cpu_guaranteed = sched_flags & SCHED_CPU_GUARANTEED
-        cpu_config = { "CPULIMIT": cpu_share, "CPUGUARANTEED": cpu_guaranteed }
-        self.update_resources(cpu_config)
+        if sched_flags & SCHED_CPU_GUARANTEED:
+            cpu_guaranteed = cpu_share
+        else:
+            cpu_guaranteed = 0
+        self.config.update('sched/fill-rate2', cpu_share)
+        self.config.update('sched/fill-rate', cpu_guaranteed)
+
         if self.vm_running:
             self.set_sched(cpu_share, sched_flags)
 
         if self.vm_running:
             self.set_sched(cpu_share, sched_flags)
 
@@ -310,11 +309,10 @@ class VServer:
     def __do_chcontext(self, state_file):
 
         if state_file:
     def __do_chcontext(self, state_file):
 
         if state_file:
-            print >>state_file, "S_CONTEXT=%u" % self.ctx
-            print >>state_file, "S_PROFILE="
+            print >>state_file, "%u" % self.ctx
             state_file.close()
 
             state_file.close()
 
-        if vserverimpl.chcontext(self.ctx):
+        if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
             self.set_resources()
             vserverimpl.setup_done(self.ctx)
 
             self.set_resources()
             vserverimpl.setup_done(self.ctx)
 
@@ -359,10 +357,8 @@ class VServer:
             raise ex
 
     def enter(self):
             raise ex
 
     def enter(self):
-
-        state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
         self.__do_chroot()
         self.__do_chroot()
-        self.__do_chcontext(state_file)
+        self.__do_chcontext(None)
 
     def start(self, wait, runlevel = 3):
         self.vm_running = True
 
     def start(self, wait, runlevel = 3):
         self.vm_running = True
@@ -376,7 +372,7 @@ class VServer:
                 os.setsid()
 
                 # open state file to record vserver info
                 os.setsid()
 
                 # open state file to record vserver info
-                state_file = open("/var/run/vservers/%s.ctx" % self.name, "w")
+                state_file = open("/var/run/vservers/%s" % self.name, "w")
 
                 # use /dev/null for stdin, /var/log/boot.log for stdout/err
                 os.close(0)
 
                 # use /dev/null for stdin, /var/log/boot.log for stdout/err
                 os.close(0)
@@ -394,39 +390,18 @@ class VServer:
 
                 # execute each init script in turn
                 # XXX - we don't support all scripts that vserver script does
 
                 # execute each init script in turn
                 # XXX - we don't support all scripts that vserver script does
-                cmd_pid = 0
-                first_child = True
-                for cmd in self.INITSCRIPTS + [None]:
-                    # wait for previous command to terminate, unless it
-                    # is the last one and the caller has specified to wait
-                    if cmd_pid and (cmd != None or wait):
-                        try:
-                            os.waitpid(cmd_pid, 0)
-                        except:
-                            print >>log, "error waiting for %s:" % cmd_pid
-                            traceback.print_exc()
-
-                    # end of list
-                    if cmd == None:
-                        os._exit(0)
-
-                    # fork and exec next command
-                    cmd_pid = os.fork()
-                    if cmd_pid == 0:
-                        try:
-                            # enter vserver context
-                            self.__do_chcontext(state_file)
-                            arg_subst = { 'runlevel': runlevel }
-                            cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
-                                                      cmd[1:])
-                            print >>log, "executing '%s'" % " ".join(cmd_args)
-                            os.execl(cmd[0], *cmd_args)
-                        except:
-                            traceback.print_exc()
-                            os._exit(1)
-                    else:
-                        # don't want to write state_file multiple times
-                        state_file = None
+                self.__do_chcontext(state_file)
+               for cmd in self.INITSCRIPTS + [None]:
+                       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_WAIT,cmd[0],*cmd_args)
+                       except:
+                               traceback.print_exc()
+                               os._exit(1)
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
@@ -443,18 +418,19 @@ class VServer:
 
         pass
 
 
         pass
 
-    def update_resources(self, resources):
-
-        self.config.update(resources)
-
-        # write new values to configuration file
-        self.__update_config_file(self.config_file, resources)
-
     def init_disk_info(self):
     def init_disk_info(self):
-
-        (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
-
-        return size
+        cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
+        (child_stdin, child_stdout, child_stderr) = os.popen3(cmd)
+        child_stdin.close()
+        line = child_stdout.readline()
+        if not line:
+            sys.stderr.write(child_stderr.readline())
+        (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)
 
     def stop(self, signal = signal.SIGKILL):
         vserverimpl.killall(self.ctx, signal)