Add support for removing resource (disk, network) limits
[util-vserver.git] / python / vserver.py
index 1a55493..7132cd1 100644 (file)
@@ -28,11 +28,6 @@ FLAGS_HIDEINFO = 32
 FLAGS_ULIMIT = 64
 FLAGS_NAMESPACE = 128
 
-# default values for new vserver scheduler
-SCHED_TOKENS_MIN = 50
-SCHED_TOKENS_MAX = 100
-SCHED_TOKENS = 100
-SCHED_INTERVAL = 1000
 
               
 class VServer:
@@ -40,7 +35,7 @@ class VServer:
     INITSCRIPTS = [('/etc/rc.vinit', 'start'),
                    ('/etc/rc.d/rc', '%(runlevel)d')]
 
-    def __init__(self, name):
+    def __init__(self, name, vm_id, vm_running = False, resources = {}):
 
         self.name = name
         self.config_file = "/etc/vservers/%s.conf" % name
@@ -48,20 +43,17 @@ class VServer:
         if not (os.path.isdir(self.dir) and
                 os.access(self.dir, os.R_OK | os.W_OK | os.X_OK)):
             raise Exception, "no such vserver: " + name
-        self.config = self.__read_config_file("/etc/vservers.conf")
-        self.config.update(self.__read_config_file(self.config_file))
-        self.flags = 0
-        flags = self.config["S_FLAGS"].split(" ")
-        if "lock" in flags:
-            self.flags |= FLAGS_LOCK
-        if "nproc" in flags:
-            self.flags |= FLAGS_NPROC
+        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.remove_caps = ~vserverimpl.CAP_SAFE;
-        self.ctx = int(self.config["S_CONTEXT"])
-
-    def __str__(self):
-
-        return self.name
+        self.ctx = vm_id
+        self.vm_running = vm_running
+        self.resources = resources
 
     config_var_re = re.compile(r"^ *([A-Z_]+)=(.*)\n?$", re.MULTILINE)
 
@@ -88,7 +80,7 @@ class VServer:
             (key, val) = m.groups()
             newval = todo.pop(key, None)
             if newval != None:
-                data = data[:m.start(2)] + newval + data[m.end(2):]
+                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)
@@ -104,33 +96,41 @@ class VServer:
         f.close()
 
         # 'copy' original file, rename new to original
-        os.link(filename, filename + ".old")
+        backup = filename + ".old"
+        try:
+            os.unlink(backup)
+        except OSError, ex:
+            if ex.errno != errno.ENOENT:
+                raise
+        os.link(filename, backup)
         os.rename(newfile, filename)
 
     def __do_chroot(self):
 
-        return os.chroot(self.dir)
+        os.chroot(self.dir)
+        os.chdir("/")
 
     def set_disklimit(self, block_limit):
 
-        # block_limit is in kB, get_disk_usage() must have been called
-        if self.disk_usage_set:
+        # block_limit is in kB
+        if block_limit == 0:
+            vserverimpl.unsetdlimit(self.dir, self.ctx)
+            return
+
+        if self.vm_running:
             block_usage = vserverimpl.DLIMIT_KEEP
             inode_usage = vserverimpl.DLIMIT_KEEP
         else:
+            # init_disk_info() must have been called to get usage values
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
-            if block_limit < block_usage:
-                raise Exception, ("%s disk usage (%u blocks) > limit (%u)" %
-                                  (self.name, block_usage, block_limit))
-            self.disk_usage_set = True
 
         vserverimpl.setdlimit(self.dir,
                               self.ctx,
                               block_usage,
                               block_limit,
                               inode_usage,
-                              -1,  # inode limit
+                              vserverimpl.DLIMIT_INF,  # inode limit
                               2)   # %age reserved for root
 
     def get_disklimit(self):
@@ -139,46 +139,21 @@ class VServer:
             blocksused, blocktotal, inodesused, inodestotal, reserved = \
                         vserverimpl.getdlimit(self.dir, self.ctx)
         except OSError, ex:
-            if ex.errno == errno.ESRCH:
-                # get here if no vserver disk limit has been set for xid
-                # set blockused to -1 to indicate no limit
-                blocktotal = -1
+            if ex.errno != errno.ESRCH:
+                raise
+            # get here if no vserver disk limit has been set for xid
+            blocktotal = -1
 
         return blocktotal
 
-    def set_sched(self, shares = 32, besteffort = True):
-        # for the old CKRM scheduler
-        if cpulimit.checkckrm() is True:
-            cpulimit.cpuinit()
-            cpulimit.vs2ckrm_on(self.name)
-            try:
-                cpulimit.cpulimit(self.name,shares)
-            except OSError, ex:
-                if ex.errno == 22:
-                    print "invalid shares argument"
-                    # should re-raise exception?!
+    def set_sched(self, cpu_share):
 
-        # for the new vserver scheduler
-        else:
-            global SCHED_TOKENS_MIN, SCHED_TOKENS_MAX, SCHED_TOKENS, SCHED_INTERVAL
-            tokensmin = SCHED_TOKENS_MIN
-            tokensmax = SCHED_TOKENS_MAX
-            tokens    = SCHED_TOKENS
-            interval  = SCHED_INTERVAL
-            fillrate = shares
-
-            if besteffort is True:
-                cpuguaranteed = 0
-            else:
-                cpuguaranteed = 1
+        if cpu_share == int(self.config.get("CPULIMIT", -1)):
+            return
 
-            try:
-                vserverimpl.setsched(self.ctx,fillrate,interval,tokens,tokensmin,tokensmax,cpuguaranteed)
-            except OSError, ex:
-                if ex.errno == 22:
-                    print "kernel does not support vserver scheduler"
-                else:
-                    raise ex
+        self.__update_config_file(self.config_file, { "CPULIMIT": cpu_share })
+        if self.vm_running:
+            vserverimpl.setsched(self.ctx, self.resources)
 
     def get_sched(self):
         # have no way of querying scheduler right now on a per vserver basis
@@ -200,11 +175,12 @@ class VServer:
         ret = vserverimpl.getrlimit(self.ctx,6)
         return ret
 
-    def set_bwlimit(self, eth, limit, cap, minrate, maxrate):
-        if cap == "-1":
-            bwlimit.off(self.ctx,eth)
+    def set_bwlimit(self, share, minrate = 1, maxrate = None, dev = "eth0"):
+
+        if share:
+            bwlimit.on(self.ctx, dev, share, minrate, maxrate)
         else:
-            bwlimit.on(self.ctx, eth, limit, cap, minrate, maxrate)
+            bwlimit.off(self.ctx, dev)
 
     def get_bwlimit(self, eth):
         # not implemented yet
@@ -271,9 +247,9 @@ class VServer:
 
         return os.fdopen(fd, mode, bufsize)
 
-    def __do_chcontext(self, state_file = None):
+    def __do_chcontext(self, state_file):
 
-        vserverimpl.chcontext(self.ctx)
+        vserverimpl.chcontext(self.ctx, self.resources)
 
         if not state_file:
             return
@@ -329,6 +305,8 @@ class VServer:
 
     def start(self, wait, runlevel = 3):
 
+        self.vm_running = True
+
         child_pid = os.fork()
         if child_pid == 0:
             # child process
@@ -346,6 +324,7 @@ class VServer:
                 self.__do_chroot()
                 log = open("/var/log/boot.log", "w", 0)
                 os.dup2(1, 2)
+                # XXX - close all other fds
 
                 print >>log, ("%s: starting the virtual server %s" %
                               (time.asctime(time.gmtime()), self.name))
@@ -356,6 +335,7 @@ class VServer:
                 # 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
@@ -398,17 +378,13 @@ class VServer:
 
     def update_resources(self, resources):
 
+        self.config.update(resources)
+
         # write new values to configuration file
         self.__update_config_file(self.config_file, resources)
 
-        #
-        # Figure out if any processes are active in context, apply new
-        # values if there are.
-        #
-
     def init_disk_info(self):
 
         (self.disk_inodes, self.disk_blocks, size) = vduimpl.vdu(self.dir)
-        self.disk_usage_set = False
 
         return size