spawnvp only takes three arguments
[util-vserver.git] / python / vserver.py
index a352427..cfd347c 100644 (file)
@@ -1,5 +1,7 @@
 # Copyright 2005 Princeton University
 
 # Copyright 2005 Princeton University
 
+#$Id: vserver.py,v 1.67 2007/07/31 18:14:02 dhozac Exp $
+
 import errno
 import fcntl
 import os
 import errno
 import fcntl
 import os
@@ -9,6 +11,8 @@ import signal
 import sys
 import time
 import traceback
 import sys
 import time
 import traceback
+import subprocess
+import resource
 
 import mountimpl
 import runcmd
 
 import mountimpl
 import runcmd
@@ -19,16 +23,6 @@ import cpulimit, bwlimit
 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
 from vserverimpl import DLIMIT_INF
 from vserverimpl import VC_LIM_KEEP
 from vserverimpl import VS_SCHED_CPU_GUARANTEED as SCHED_CPU_GUARANTEED
 from vserverimpl import DLIMIT_INF
 from vserverimpl import VC_LIM_KEEP
-
-from vserverimpl import RLIMIT_CPU
-from vserverimpl import RLIMIT_RSS
-from vserverimpl import RLIMIT_NPROC
-from vserverimpl import RLIMIT_NOFILE
-from vserverimpl import RLIMIT_MEMLOCK
-from vserverimpl import RLIMIT_AS
-from vserverimpl import RLIMIT_LOCKS
-from vserverimpl import RLIMIT_SIGPENDING
-from vserverimpl import RLIMIT_MSGQUEUE
 from vserverimpl import VLIMIT_NSOCK
 from vserverimpl import VLIMIT_OPENFD
 from vserverimpl import VLIMIT_ANON
 from vserverimpl import VLIMIT_NSOCK
 from vserverimpl import VLIMIT_OPENFD
 from vserverimpl import VLIMIT_ANON
@@ -46,71 +40,66 @@ FLAGS_HIDEINFO = 32
 FLAGS_ULIMIT = 64
 FLAGS_NAMESPACE = 128
 
 FLAGS_ULIMIT = 64
 FLAGS_NAMESPACE = 128
 
-RLIMITS = {"CPU": RLIMIT_CPU,
-           "RSS": RLIMIT_RSS,
-           "NPROC": RLIMIT_NPROC,
-           "NOFILE": RLIMIT_NOFILE,
-           "MEMLOCK": RLIMIT_MEMLOCK,
-           "AS": RLIMIT_AS,
-           "LOCKS": RLIMIT_LOCKS,
-           "SIGPENDING": RLIMIT_SIGPENDING,
-           "MSGQUEUE": RLIMIT_MSGQUEUE,
-           "NSOCK": VLIMIT_NSOCK,
-           "OPENFD": VLIMIT_OPENFD,
-           "ANON": VLIMIT_ANON,
-           "SHMEM": VLIMIT_SHMEM}
+RLIMITS = { "NSOCK": VLIMIT_NSOCK,
+            "OPENFD": VLIMIT_OPENFD,
+            "ANON": VLIMIT_ANON,
+            "SHMEM": VLIMIT_SHMEM}
+
+# add in the platform supported rlimits
+for entry in resource.__dict__.keys():
+    if entry.find("RLIMIT_")==0:
+        k = entry[len("RLIMIT_"):]
+        if not RLIMITS.has_key(k):
+            RLIMITS[k]=resource.__dict__[entry]
+        else:
+            print "WARNING: duplicate RLIMITS key %s" % k
 
 class NoSuchVServer(Exception): pass
 
 
 class VServerConfig:
 
 class NoSuchVServer(Exception): pass
 
 
 class VServerConfig:
-    mapping = {
-        'S_CONTEXT':           'context',
-        'VS_WHITELISTED':      'whitelisted',
-        'CPULIMIT':            'sched/fill-rate2',
-       'CPUGUARANTEED':        'sched/fill-rate',
-        'VS_DISK_MAX':         'dlimits/0/space_total',
-      }
-
     def __init__(self, name, directory):
     def __init__(self, name, directory):
-        global RLIMITS
-        for i in RLIMITS.keys():
-            for j in ('HARD', 'SOFT', 'MINIMUM'):
-                self.mapping['VS_%s_%s' % (i, j)] = 'rlimits/%s.%s' % (i.lower(), j.replace('MINIMUM', 'min').lower())
         self.name = name
         self.dir = directory
 
     def get(self, option, default = None):
         try:
         self.name = name
         self.dir = directory
 
     def get(self, option, default = None):
         try:
-            filename = self.mapping[option]
-            f = open(os.path.join(self.dir, filename), "r")
+            f = open(os.path.join(self.dir, option), "r")
             buf = f.readline().rstrip()
             f.close()
             return buf
             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)
 
         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, vars):
-        for (option, value) in vars.iteritems():
+    def update(self, option, value):
+        try:
+            old_umask = os.umask(0022)
+            filename = os.path.join(self.dir, option)
             try:
             try:
-                old_umask = os.umask(0022)
-                filename = os.path.join(self.dir, self.mapping[option])
-                try:
-                    os.makedirs(os.path.dirname(filename), 0755)
-                except:
-                    pass
-                f = open(filename, 'w')
+                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.write("%s\n" % value)
-                f.close()
-                os.umask(old_umask)
-            except KeyError, e:
-                raise KeyError, "Don't know how to handle %s, sorry" % option
+            f.close()
+            os.umask(old_umask)
+        except:
+            raise
+
+    def unset(self, option):
+        try:
+            filename = os.path.join(self.dir, option)
+            os.unlink(filename)
+            os.removedirs(os.path.dirname(filename))
+            return True
+        except:
+            return False
 
 
 class VServer:
 
 
 class VServer:
@@ -129,7 +118,7 @@ class VServer:
         self.config = VServerConfig(name, "/etc/vservers/%s" % name)
         self.remove_caps = ~vserverimpl.CAP_SAFE;
         if vm_id == None:
         self.config = VServerConfig(name, "/etc/vservers/%s" % name)
         self.remove_caps = ~vserverimpl.CAP_SAFE;
         if vm_id == None:
-            vm_id = int(self.config.get('S_CONTEXT'))
+            vm_id = int(self.config.get('context'))
         self.ctx = vm_id
         if vm_running == None:
             vm_running = self.is_running()
         self.ctx = vm_id
         if vm_running == None:
             vm_running = self.is_running()
@@ -159,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):
@@ -183,14 +169,55 @@ 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)
+        self.config.update('whitelisted', whitelisted)
+
+    def set_capabilities(self, capabilities):
+        return vserverimpl.setbcaps(self.ctx, vserverimpl.text2bcaps(capabilities))
+
+    def set_capabilities_config(self, capabilities):
+        self.config.update('bcapabilities', capabilities)
+        self.set_capabilities(capabilities)
+
+    def get_capabilities(self):
+        return vserverimpl.bcaps2text(vserverimpl.getbcaps(self.ctx))
+    def get_capabilities_config(self):
+        return self.config.get('bcapabilities', '')
+
+    def set_ipaddresses(self, addresses):
+        vserverimpl.netremove(self.ctx, "all")
+        for a in addresses.split(","):
+            vserverimpl.netadd(self.ctx, a)
+
+    def set_ipaddresses_config(self, addresses):
+        i = 0
+        for a in addresses.split(","):
+            self.config.update("interfaces/%d/ip" % i, a)
+            i += 1
+        while self.config.unset("interfaces/%d/ip" % i):
+            i += 1
+        self.set_ipaddresses(addresses)
+
+    def get_ipaddresses_config(self):
+        i = 0
+        ret = []
+        while True:
+            r = self.config.get("interfaces/%d/ip" % i, '')
+            if r == '':
+                break
+            ret += [r]
+            i += 1
+        return ",".join(ret)
+
+    def get_ipaddresses(self):
+        # No clean way to do this right now.
+        return None
 
     def __do_chroot(self):
 
 
     def __do_chroot(self):
 
@@ -244,8 +271,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,9 +295,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. """
 
-        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)
 
@@ -311,7 +341,7 @@ class VServer:
             print >>state_file, "%u" % self.ctx
             state_file.close()
 
             print >>state_file, "%u" % self.ctx
             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)
 
@@ -332,8 +362,9 @@ class VServer:
                          ([], filter_fn))[0]
         garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
                                               os.listdir(LOCKDIR)))
                          ([], filter_fn))[0]
         garbage += filter(os.path.isfile, map((LOCKDIR + "/").__add__,
                                               os.listdir(LOCKDIR)))
-        for f in garbage:
-            os.unlink(f)
+        if False:
+            for f in garbage:
+                os.unlink(f)
 
         # set the initial runlevel
         f = open(RUNDIR + "/utmp", "w")
 
         # set the initial runlevel
         f = open(RUNDIR + "/utmp", "w")
@@ -374,11 +405,14 @@ class VServer:
                 state_file = open("/var/run/vservers/%s" % self.name, "w")
 
                 # use /dev/null for stdin, /var/log/boot.log for stdout/err
                 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)
-                os.close(1)
-                os.open("/dev/null", os.O_RDONLY)
+                fd = os.open("/dev/null", os.O_RDONLY)
+                if fd != 0:
+                    os.dup2(fd, 0)
+                    os.close(fd)
                 self.__do_chroot()
                 log = open("/var/log/boot.log", "w", 0)
                 self.__do_chroot()
                 log = open("/var/log/boot.log", "w", 0)
+                if log.fileno() != 1:
+                    os.dup2(log.fileno(), 1)
                 os.dup2(1, 2)
 
                 print >>log, ("%s: starting the virtual server %s" %
                 os.dup2(1, 2)
 
                 print >>log, ("%s: starting the virtual server %s" %
@@ -390,17 +424,17 @@ class VServer:
                 # execute each init script in turn
                 # XXX - we don't support all scripts that vserver script does
                 self.__do_chcontext(state_file)
                 # 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 + [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)
+                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:
@@ -417,17 +451,19 @@ class VServer:
 
         pass
 
 
         pass
 
-    def update_resources(self, resources):
-
-        self.config.update(resources)
-
     def init_disk_info(self):
         cmd = "/usr/sbin/vdu --script --space --inodes --blocksize 1024 --xid %d %s" % (self.ctx, self.dir)
     def init_disk_info(self):
         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()
+        p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                             close_fds=True)
+        p.stdin.close()
+        line = p.stdout.readline()
         if not line:
         if not line:
-            sys.stderr.write(child_stderr.readline())
+            sys.stderr.write(p.stderr.read())
+        p.stdout.close()
+        p.stderr.close()
+        ret = p.wait()
+
         (space, inodes) = line.split()
         self.disk_inodes = int(inodes)
         self.disk_blocks = int(space)
         (space, inodes) = line.split()
         self.disk_inodes = int(inodes)
         self.disk_blocks = int(space)