Don't clean stuff up if it's already running.
[util-vserver-pl.git] / python / vserver.py
index dbb7853..aee6d03 100644 (file)
@@ -17,7 +17,6 @@ import resource
 import vserverimpl
 import cpulimit, bwlimit
 
 import vserverimpl
 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 DLIMIT_INF
 from vserverimpl import VC_LIM_KEEP
 from vserverimpl import VLIMIT_NSOCK
@@ -130,6 +129,26 @@ class VServerConfig:
         os.path.walk(self.dir, add_to_cache, self.cache)
 
 
         os.path.walk(self.dir, add_to_cache, self.cache)
 
 
+def adjust_lim(goal, curr):
+    gh = goal[0]
+    gs = goal[1]
+    gm = goal[2]
+    soft = curr[0]
+    hard = curr[1]
+    if gm != VC_LIM_KEEP:
+        if gm > soft or gm == resource.RLIM_INFINITY:
+            soft = gm
+        if gm > hard or gm == resource.RLIM_INFINITY:
+            hard = gm
+    if gs != VC_LIM_KEEP:
+        if gs > soft or gs == resource.RLIM_INFINITY:
+            soft = gs
+    if gh != VC_LIM_KEEP:
+        if gh > hard or gh == resource.RLIM_INFINITY:
+            hard = gh
+    return (soft, hard)
+
+
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
 class VServer:
 
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
@@ -138,7 +157,6 @@ class VServer:
     def __init__(self, name, vm_id = None, vm_running = None, logfile=None):
 
         self.name = name
     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)):
         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)):
@@ -164,68 +182,68 @@ class VServer:
             except:
                 print '%s: (%s failed to open) %s'%(time.asctime(time.gmtime()),self.logfile,msg)
 
             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
         """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)
         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 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)
+                if hasattr(resource, 'RLIMIT_' + type):
+                    lim = resource.getrlimit(resource_type)
+                    lim = adjust_lim((hard, soft, min), lim)
+                    resource.setrlimit(resource_type, lim)
+            except OSError, e:
+                self.log("Error: setrlimit(%d, %s, %d, %d, %d): %s"
+                         % (self.ctx, type.lower(), hard, soft, min))
+
+        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):
 
     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)
+        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):
 
     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):
  
     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")
 
     def set_ipaddresses(self, addresses):
         vserverimpl.netremove(self.ctx, "all")
@@ -283,7 +301,7 @@ class VServer:
             try:
                 vserverimpl.unsetdlimit(self.dir, self.ctx)
             except OSError, e:
             try:
                 vserverimpl.unsetdlimit(self.dir, self.ctx)
             except OSError, e:
-                self.log("Unexpected error with unsetdlimit for context %d" % self.ctx)
+                self.log("Unexpected error with unsetdlimit for context %d: %r" % (self.ctx,e))
             return
 
         if self.vm_running:
             return
 
         if self.vm_running:
@@ -294,7 +312,6 @@ class VServer:
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
-
         try:
             vserverimpl.setdlimit(self.dir,
                                   self.ctx,
         try:
             vserverimpl.setdlimit(self.dir,
                                   self.ctx,
@@ -304,7 +321,7 @@ class VServer:
                                   vserverimpl.DLIMIT_INF,  # inode limit
                                   2)   # %age reserved for root
         except OSError, e:
                                   vserverimpl.DLIMIT_INF,  # inode limit
                                   2)   # %age reserved for root
         except OSError, e:
-            self.log("Unexpected error with setdlimit for context %d" % self.ctx)
+            self.log("Unexpected error with setdlimit for context %d: %r" % (self.ctx, e))
 
 
         self.config.update('dlimits/0/space_total', block_limit)
 
 
         self.config.update('dlimits/0/space_total', block_limit)
@@ -325,25 +342,23 @@ class VServer:
 
         return block_limit
 
 
         return block_limit
 
-    def set_sched_config(self, cpu_share, sched_flags):
+    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. """
 
 
         """ Write current CPU scheduler parameters to the vserver
         configuration file. This method does not modify the kernel CPU
         scheduling parameters for this context. """
 
-        if sched_flags & SCHED_CPU_GUARANTEED:
-            cpu_guaranteed = cpu_share
-        else:
-            cpu_guaranteed = 0
+        self.config.update('sched/fill-rate', cpu_min)
         self.config.update('sched/fill-rate2', cpu_share)
         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 cpu_share == 0:
+            self.config.unset('sched/idle-time')
+        
+        if self.is_running():
+            self.set_sched(cpu_min, cpu_share)
 
 
-    def set_sched(self, cpu_share, sched_flags = 0):
+    def set_sched(self, cpu_min, cpu_share):
         """ Update kernel CPU scheduling parameters for this context. """
         """ Update kernel CPU scheduling parameters for this context. """
-        vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
+        vserverimpl.setsched(self.ctx, cpu_min, cpu_share)
 
     def get_sched(self):
         # have no way of querying scheduler right now on a per vserver basis
 
     def get_sched(self):
         # have no way of querying scheduler right now on a per vserver basis
@@ -378,40 +393,47 @@ class VServer:
             state_file.close()
 
         if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
             state_file.close()
 
         if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
-            self.set_resources()
+            self.set_resources(True)
             vserverimpl.setup_done(self.ctx)
 
             vserverimpl.setup_done(self.ctx)
 
-    def __prep(self, runlevel, log):
+
+    def __prep(self, runlevel):
 
         """ Perform all the crap that the vserver script does before
         actually executing the startup scripts. """
 
 
         """ 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
 
         # set the initial runlevel
-        vserverimpl.setrunlevel(RUNDIR + "/utmp", runlevel)
+        vserverimpl.setrunlevel(self.dir + "/var/run/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")
 
 
         # 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):
 
 
+    def __cleanvar(self):
+        """
+        Clean the /var/ directory so RH startup scripts can run
+        """ 
+
+        RUNDIR = "/var/run"
+        LOCKDIR = "/var/lock/subsys"
+
+        filter = ["utmp"]
+        garbage = []
+        for topdir in [RUNDIR, LOCKDIR]:
+            #os.walk() = (dirpath, dirnames, filenames)
+            for root, dirs, files in os.walk(topdir):
+                for file in files:
+                    if not file in filter:
+                        garbage.append(root + "/" + file)
+
+        for f in garbage: os.unlink(f)
+        return garbage
+
+
+    def __do_mount(self, *mount_args):
         try:
             vserverimpl.mount(*mount_args)
         except OSError, ex:
         try:
             vserverimpl.mount(*mount_args)
         except OSError, ex:
@@ -420,67 +442,77 @@ class VServer:
                 return
             raise ex
 
                 return
             raise ex
 
+
     def enter(self):
         self.config.cache_it()
         self.__do_chroot()
         self.__do_chcontext(None)
 
     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:
+    def start(self, runlevel = 3):
+
+        if (os.fork() != 0):
+            # Parent should just return.
+            self.vm_running = True
+            return
+        else:
             # child process
             try:
             # child process
             try:
+                # so we don't chcontext with priv'ed fds
+                close_nonstandard_fds()
+
                 # get a new session
                 os.setsid()
 
                 # open state file to record vserver info
                 state_file = open("/var/run/vservers/%s" % self.name, "w")
 
                 # get a new session
                 os.setsid()
 
                 # open state file to record vserver info
                 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/nm for stdout/err
                 fd = os.open("/dev/null", os.O_RDONLY)
                 if fd != 0:
                     os.dup2(fd, 0)
                     os.close(fd)
                 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()
                 self.config.cache_it()
                 self.__do_chroot()
-                log = open("/var/log/boot.log", "a", 0)
+                if not self.is_running():
+                    removed = self.__cleanvar()
+
+                log = open("/var/log/nm", "a", 0)
                 if log.fileno() != 1:
                     os.dup2(log.fileno(), 1)
                 os.dup2(1, 2)
 
                 if log.fileno() != 1:
                     os.dup2(log.fileno(), 1)
                 os.dup2(1, 2)
 
+                print >>log, ("%s: removing %s" % 
+                                (time.asctime(time.gmtime()), removed))
                 print >>log, ("%s: starting the virtual server %s" %
                 print >>log, ("%s: starting the virtual server %s" %
-                              (time.asctime(time.gmtime()), self.name))
-
-                # perform pre-init cleanup
-                self.__prep(runlevel, log)
-
+                                (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:
                 # 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,
+                    try:
+                        # enter vserver context
+                        arg_subst = { 'runlevel': runlevel }
+                        cmd_args = [cmd[0]] + map(lambda x: x % arg_subst,
                                                    cmd[1:])
                                                    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)
+                        if os.path.isfile(cmd[0]):                         
+                            print >>log, "executing '%s'" % " ".join(cmd_args)
+                            os.spawnvp(os.P_NOWAIT,cmd[0],cmd_args)
+                        else:
+                            print >>log, "WARNING: could not run %s"%cmd[0]
+                    except:
+                        print >>log, traceback.format_exc()
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
                 self.log(traceback.format_exc())
             os._exit(0)
 
 
             # we get here due to an exception in the top-level child process
             except Exception, ex:
                 self.log(traceback.format_exc())
             os._exit(0)
 
-        # parent process
-        return child_pid
-
-    def set_resources(self):
+    def set_resources(self,setup=False):
 
         """ Called when vserver context is entered for first time,
         should be overridden by subclass. """
 
         """ Called when vserver context is entered for first time,
         should be overridden by subclass. """
@@ -488,6 +520,13 @@ class VServer:
         pass
 
     def init_disk_info(self):
         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,
         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,
@@ -510,8 +549,14 @@ class VServer:
     def stop(self, signal = signal.SIGKILL):
         vserverimpl.killall(self.ctx, signal)
         self.vm_running = False
     def stop(self, signal = signal.SIGKILL):
         vserverimpl.killall(self.ctx, signal)
         self.vm_running = False
-        self.rlimits_changed = False
 
 
+    def setname(self, slice_id):
+        '''Set vcVHI_CONTEXT field in kernel to slice_id'''
+        vserverimpl.setname(self.ctx, slice_id)
+
+    def getname(self):
+        '''Get vcVHI_CONTEXT field in kernel'''
+        return vserverimpl.getname(self.ctx)
 
 
 def create(vm_name, static = False, ctor = VServer):
 
 
 def create(vm_name, static = False, ctor = VServer):
@@ -530,3 +575,12 @@ def create(vm_name, static = False, ctor = VServer):
     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)
+
+
+def close_nonstandard_fds():
+    """Close all open file descriptors other than 0, 1, and 2."""
+    _SC_OPEN_MAX = 4
+    for fd in range(3, os.sysconf(_SC_OPEN_MAX)):
+        try: os.close(fd)
+        except OSError: pass  # most likely an fd that isn't open
+