Don't wait for the initscripts to complete.
[util-vserver.git] / python / vserver.py
index 139a953..de8c164 100644 (file)
@@ -1,5 +1,7 @@
 # Copyright 2005 Princeton University
 
 # Copyright 2005 Princeton University
 
+#$Id: vserver.py,v 1.72 2007/08/02 16:01:59 dhozac Exp $
+
 import errno
 import fcntl
 import os
 import errno
 import fcntl
 import os
@@ -9,16 +11,19 @@ import signal
 import sys
 import time
 import traceback
 import sys
 import time
 import traceback
+import subprocess
+import resource
 
 
-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
-
-
+from vserverimpl import DLIMIT_INF
+from vserverimpl import VC_LIM_KEEP
+from vserverimpl import VLIMIT_NSOCK
+from vserverimpl import VLIMIT_OPENFD
+from vserverimpl import VLIMIT_ANON
+from vserverimpl import VLIMIT_SHMEM
 
 #
 # these are the flags taken from the kernel linux/vserver/legacy.h
 
 #
 # these are the flags taken from the kernel linux/vserver/legacy.h
@@ -32,88 +37,215 @@ FLAGS_HIDEINFO = 32
 FLAGS_ULIMIT = 64
 FLAGS_NAMESPACE = 128
 
 FLAGS_ULIMIT = 64
 FLAGS_NAMESPACE = 128
 
-
+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 NoSuchVServer(Exception): pass
 
 
+class VServerConfig:
+    def __init__(self, name, directory):
+        self.name = name
+        self.dir = directory
+        self.cache = None
+        if not (os.path.isdir(self.dir) and
+                os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
+            raise NoSuchVServer, "%s does not exist" % self.dir
+
+    def get(self, option, default = None):
+        try:
+            if self.cache:
+                return self.cache[option]
+            else:
+                f = open(os.path.join(self.dir, option), "r")
+                buf = f.read().rstrip()
+                f.close()
+                return buf
+        except:
+            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):
+        if self.cache:
+            return
+
+        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:
+            raise
+
+    def unset(self, option):
+        if self.cache:
+            return
+
+        try:
+            filename = os.path.join(self.dir, option)
+            os.unlink(filename)
+            try:
+                os.removedirs(os.path.dirname(filename))
+            except:
+                pass
+            return True
+        except:
+            return False
+
+    def cache_it(self):
+        self.cache = {}
+        def add_to_cache(cache, dirname, fnames):
+            for file in fnames:
+                full_name = os.path.join(dirname, file)
+                if os.path.islink(full_name):
+                    fnames.remove(file)
+                elif (os.path.isfile(full_name) and
+                      os.access(full_name, os.R_OK)):
+                    f = open(full_name, "r")
+                    cache[full_name.replace(os.path.join(self.dir, ''),
+                                            '')] = f.read().rstrip()
+                    f.close()
+        os.path.walk(self.dir, add_to_cache, self.cache)
+
 
 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, vm_running = False):
+    def __init__(self, name, vm_id = None, vm_running = None):
 
         self.name = name
 
         self.name = name
-        self.config_file = "/etc/vservers/%s.conf" % 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.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;
         self.remove_caps = ~vserverimpl.CAP_SAFE;
+        if vm_id == None:
+            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
 
         self.vm_running = vm_running
 
-    config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
+    def have_limits_changed(self):
+        return self.rlimits_changed
 
 
-    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):
-
-        # read old file, apply changes
-        f = open(filename, "r")
-        data = f.read()
-        f.close()
-        todo = newvars.copy()
+    def set_rlimit_limit(self,type,hard,soft,minimum):
+        """Generic set resource limit function for vserver"""
+        global RLIMITS
         changed = False
         changed = False
-        for m in self.config_var_re.finditer(data):
-            (key, val) = m.groups()
-            newval = todo.pop(key, None)
-            if newval != None:
-                data = data[:m.start(2)] + str(newval) + data[m.end(2):]
-                changed = True
-        for (newkey, newval) in todo.items():
-            data += "%s=%s\n" % (newkey, newval)
-            changed = True
-
-        if not changed:
-            return
-
-        # write new file
-        newfile = filename + ".new"
-        f = open(newfile, "w")
-        f.write(data)
-        f.close()
-
-        # 'copy' original file, rename new to original
-        backup = filename + ".old"
         try:
         try:
-            os.unlink(backup)
-        except OSError, ex:
-            if ex.errno != errno.ENOENT:
-                raise
-        os.link(filename, backup)
-        os.rename(newfile, filename)
+            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(): print "Unexpected error with getrlimit for running context %d" % self.ctx
+
+        resource_type = RLIMITS[type]
+        try:
+            ret = vserverimpl.setrlimit(self.ctx,resource_type,hard,soft,minimum)
+        except OSError, e:
+            if self.is_running(): print "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)
+        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)
+
+    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:
+            print "Unexpected error with getrlimit for context %d" % self.ctx
+            ret = self.get_rlimit_config(type)
+        return ret
 
 
-    def __do_chroot(self):
+    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 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):
+        self.config.cache_it()
         os.chroot(self.dir)
         os.chdir("/")
 
         os.chroot(self.dir)
         os.chdir("/")
 
@@ -135,10 +267,12 @@ class VServer:
         return result
 
     def set_disklimit(self, block_limit):
         return result
 
     def set_disklimit(self, block_limit):
-
         # block_limit is in kB
         if block_limit == 0:
         # block_limit is in kB
         if block_limit == 0:
-            vserverimpl.unsetdlimit(self.dir, self.ctx)
+            try:
+                vserverimpl.unsetdlimit(self.dir, self.ctx)
+            except OSError, e:
+                print "Unexpected error with unsetdlimit for context %d" % self.ctx
             return
 
         if self.vm_running:
             return
 
         if self.vm_running:
@@ -149,14 +283,24 @@ class VServer:
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
-        vserverimpl.setdlimit(self.dir,
-                              self.ctx,
-                              block_usage,
-                              block_limit,
-                              inode_usage,
-                              vserverimpl.DLIMIT_INF,  # inode limit
-                              2)   # %age reserved for root
 
 
+        try:
+            vserverimpl.setdlimit(self.dir,
+                                  self.ctx,
+                                  block_usage,
+                                  block_limit,
+                                  inode_usage,
+                                  vserverimpl.DLIMIT_INF,  # inode limit
+                                  2)   # %age reserved for root
+        except OSError, e:
+            print "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)
+    
     def get_disklimit(self):
 
         try:
     def get_disklimit(self):
 
         try:
@@ -176,51 +320,41 @@ 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)
 
     def set_sched(self, cpu_share, sched_flags = 0):
         if self.vm_running:
             self.set_sched(cpu_share, sched_flags)
 
     def set_sched(self, cpu_share, sched_flags = 0):
-
         """ Update kernel CPU scheduling parameters for this context. """
         """ Update kernel CPU scheduling parameters for this context. """
-
         vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
 
     def get_sched(self):
         # have no way of querying scheduler right now on a per vserver basis
         return (-1, False)
 
         vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
 
     def get_sched(self):
         # have no way of querying scheduler right now on a per vserver basis
         return (-1, False)
 
-    def set_memlimit(self, limit):
-        ret = vserverimpl.setrlimit(self.ctx,5,limit)
-        return ret
-
-    def get_memlimit(self):
-        ret = vserverimpl.getrlimit(self.ctx,5)
-        return ret
-    
-    def set_tasklimit(self, limit):
-        ret = vserverimpl.setrlimit(self.ctx,6,limit)
-        return ret
-
-    def get_tasklimit(self):
-        ret = vserverimpl.getrlimit(self.ctx,6)
-        return ret
-
-    def set_bwlimit(self, maxrate, minrate = 1, share = None, dev = "eth0"):
+    def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
+                    exempt_min = None, exempt_max = None,
+                    share = None, dev = "eth0"):
 
 
-        if maxrate != 0:
-            bwlimit.on(self.ctx, dev, share, minrate, maxrate)
-        else:
+        if minrate is None:
             bwlimit.off(self.ctx, dev)
             bwlimit.off(self.ctx, dev)
+        else:
+            bwlimit.on(self.ctx, dev, share,
+                       minrate, maxrate, exempt_min, exempt_max)
 
     def get_bwlimit(self, dev = "eth0"):
 
 
     def get_bwlimit(self, dev = "eth0"):
 
+        result = bwlimit.get(self.ctx)
         # result of bwlimit.get is (ctx, share, minrate, maxrate)
         # result of bwlimit.get is (ctx, share, minrate, maxrate)
-        return bwlimit.get(self.ctx)[1:]
+        if result:
+            result = result[1:]
+        return result
 
     def open(self, filename, mode = "r", bufsize = -1):
 
 
     def open(self, filename, mode = "r", bufsize = -1):
 
@@ -229,11 +363,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)
 
@@ -254,23 +387,24 @@ 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")
-        utmp.set_runlevel(f, runlevel)
+        vserverimpl.setrunlevel(f, runlevel)
         f.close()
 
         # mount /proc and /dev/pts
         f.close()
 
         # mount /proc and /dev/pts
-        self.__do_mount("none", "/proc", "proc")
+        self.__do_mount("none", self.dir, "/proc", "proc")
         # XXX - magic mount options
         # XXX - magic mount options
-        self.__do_mount("none", "/dev/pts", "devpts", 0, "gid=5,mode=0620")
+        self.__do_mount("none", self.dir, "/dev/pts", "devpts", 0, "gid=5,mode=0620")
 
     def __do_mount(self, *mount_args):
 
         try:
 
     def __do_mount(self, *mount_args):
 
         try:
-            mountimpl.mount(*mount_args)
+            vserverimpl.mount(*mount_args)
         except OSError, ex:
             if ex.errno == errno.EBUSY:
                 # assume already mounted
         except OSError, ex:
             if ex.errno == errno.EBUSY:
                 # assume already mounted
@@ -278,14 +412,12 @@ 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):
 
     def start(self, wait, runlevel = 3):
-
         self.vm_running = True
         self.vm_running = True
+        self.rlimits_changed = False
 
         child_pid = os.fork()
         if child_pid == 0:
 
         child_pid = os.fork()
         if child_pid == 0:
@@ -295,14 +427,17 @@ 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
 
                 # 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" %
@@ -313,39 +448,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:
+                     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:
+                         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:
@@ -362,32 +476,46 @@ 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)
+        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:
+            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)
+        #(self.disk_inodes, self.disk_blocks) = vduimpl.vdu(self.dir)
+
+        return self.disk_blocks * 1024
 
     def stop(self, signal = signal.SIGKILL):
 
     def stop(self, signal = signal.SIGKILL):
-
         vserverimpl.killall(self.ctx, signal)
         self.vm_running = False
         vserverimpl.killall(self.ctx, signal)
         self.vm_running = False
+        self.rlimits_changed = False
 
 
 
 def create(vm_name, static = False, ctor = VServer):
 
 
 
 
 def create(vm_name, static = False, ctor = VServer):
 
-    options = []
+    options = ['vuseradd']
     if static:
         options += ['--static']
     if static:
         options += ['--static']
-    runcmd.run('vuseradd', options + [vm_name])
+    ret = os.spawnvp(os.P_WAIT, 'vuseradd', options + [vm_name])
+    if !os.WIFEXITED(ret) || os.WEXITSTATUS(ret) != 0:
+        out = "system command ('%s') " % options
+        if os.WIFEXITED(ret):
+            out += "failed, rc = %d" % os.WEXITSTATUS(ret)
+        else:
+            out += "killed by signal %d" % os.WTERMSIG(ret)
+        raise SystemError, out
     vm_id = pwd.getpwnam(vm_name)[2]
 
     return ctor(vm_name, vm_id)
     vm_id = pwd.getpwnam(vm_name)[2]
 
     return ctor(vm_name, vm_id)