Setting tag util-vserver-pl-0.4-29
[util-vserver-pl.git] / python / vserver.py
index dbb7853..b2ad6a8 100644 (file)
@@ -12,14 +12,16 @@ import sys
 import time
 import traceback
 import subprocess
+import commands
 import resource
 
 import vserverimpl
-import cpulimit, bwlimit
+import cpulimit
+import plnode.bwlimit as 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 VC_LIM_INFINITY
 from vserverimpl import VLIMIT_NSOCK
 from vserverimpl import VLIMIT_OPENFD
 from vserverimpl import VLIMIT_ANON
@@ -42,6 +44,8 @@ RLIMITS = { "NSOCK": VLIMIT_NSOCK,
             "ANON": VLIMIT_ANON,
             "SHMEM": VLIMIT_SHMEM}
 
+CPU_SHARE_MULT = 1024
+
 # add in the platform supported rlimits
 for entry in resource.__dict__.keys():
     if entry.find("RLIMIT_")==0:
@@ -53,7 +57,6 @@ for entry in resource.__dict__.keys():
 
 class NoSuchVServer(Exception): pass
 
-
 class VServerConfig:
     def __init__(self, name, directory):
         self.name = name
@@ -132,19 +135,15 @@ class VServerConfig:
 
 class VServer:
 
-    INITSCRIPTS = [('/etc/rc.vinit', 'start'),
-                   ('/etc/rc.d/rc', '%(runlevel)d')]
-
     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)):
             raise NoSuchVServer, "no such vserver: " + name
         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
@@ -154,92 +153,103 @@ class VServer:
         self.logfile = logfile
 
     # inspired from nodemanager's logger
+    def log_in_file (self, fd, msg):
+        if not msg: msg="\n"
+        if not msg.endswith('\n'): msg += '\n'
+        os.write(fd, '%s: %s' % (time.asctime(time.gmtime()), msg))
+
     def log(self,msg):
         if self.logfile:
             try:
                 fd = os.open(self.logfile,os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
-                if not msg.endswith('\n'): msg += '\n'
-                os.write(fd, '%s: %s' % (time.asctime(time.gmtime()), msg))
+                self.log_in_file(fd,msg)
                 os.close(fd)
             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
-        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)
+            update = True
         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)
+            except OSError, e:
+                self.log("Error: setrlimit(%d, %s, %d, %d, %d): %s"
+                         % (self.ctx, type.lower(), hard, soft, min, e))
+
+        return update
+
+    def get_prefix_from_capabilities(self, capabilities, prefix):
+        split_caps = capabilities.split(',')
+        return ",".join(["%s" % (c[len(prefix):]) 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):
-        self.config.update('bcapabilities', capabilities)
-        self.set_capabilities(capabilities)
+        bcaps = self.get_bcaps_from_capabilities(capabilities)
+        ccaps = self.get_ccaps_from_capabilities(capabilities)
+        if len(bcaps) > 0:
+            bcaps += ","
+        bcaps += "CAP_NET_RAW"
+        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):
-        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):
-        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")
-        for a in addresses.split(","):
-            vserverimpl.netadd(self.ctx, a)
+        for ip in addresses:
+            vserverimpl.netadd(self.ctx, ip)
+
+    def set_ipaddresses_config(self, addresses, add_loopback=True):
+        ip_addresses = addresses.split(",")
+
+        # add looopback interface
+        if not ip_addresses.__contains__("127.0.0.1") and add_loopback:
+            ip_addresses.append("127.0.0.1")
 
-    def set_ipaddresses_config(self, addresses):
         i = 0
-        for a in addresses.split(","):
-            self.config.update("interfaces/%d/ip" % i, a)
+        for ip in ip_addresses:
+            self.config.update("interfaces/%d/ip" % i, ip)
+            # create emtpy nodev files to silent "No device specified for" warnings
+            self.config.update("interfaces/%d/nodev" % i, "")
             i += 1
-        while self.config.unset("interfaces/%d/ip" % i):
+        while self.config.unset("interfaces/%d/ip" % i) and self.config.update("interfaces/%d/nodev" % i, ""):
             i += 1
-        self.set_ipaddresses(addresses)
+        self.set_ipaddresses(ip_addresses)
 
     def get_ipaddresses_config(self):
         i = 0
@@ -254,20 +264,20 @@ class VServer:
 
     def get_ipaddresses(self):
         # No clean way to do this right now.
+        self.log("Calling Vserver.get_ipaddresses for slice %s" % self.name)
         return None
 
     def __do_chroot(self):
         os.chroot(self.dir)
         os.chdir("/")
 
-    def chroot_call(self, fn, *args):
-
+    def chroot_call(self, fn, *args, **kwargs):
         cwd_fd = os.open(".", os.O_RDONLY)
         try:
             root_fd = os.open("/", os.O_RDONLY)
             try:
                 self.__do_chroot()
-                result = fn(*args)
+                result = fn(*args, **kwargs)
             finally:
                 os.fchdir(root_fd)
                 os.chroot(".")
@@ -294,7 +304,6 @@ class VServer:
             block_usage = self.disk_blocks
             inode_usage = self.disk_inodes
 
-
         try:
             vserverimpl.setdlimit(self.dir,
                                   self.ctx,
@@ -306,14 +315,13 @@ class VServer:
         except OSError, e:
             self.log("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)
+        status = subprocess.call(["/usr/sbin/vserver", self.name, "running"], shell=False)
+        return not status
     
     def get_disklimit(self):
-
         try:
             (self.disk_blocks, block_limit, self.disk_inodes, inode_limit,
              reserved) = vserverimpl.getdlimit(self.dir, self.ctx)
@@ -325,29 +333,29 @@ class VServer:
 
         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. """
-
-        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):
-        """ Update kernel CPU scheduling parameters for this context. """
-        vserverimpl.setsched(self.ctx, cpu_share, sched_flags)
+        configuration file. Currently, 'cpu_min' is not supported. """
+        self.config.update('cgroup/cpu.shares', int(cpu_share) * CPU_SHARE_MULT)
+        if self.is_running():
+            self.set_sched(cpu_min, cpu_share)
+
+    def set_sched(self, cpu_min, cpu_share):
+        """ Update kernel CPU scheduling parameters for this context.
+        Currently, 'cpu_min' is not supported. """
+        try:
+            cgroup = open('/dev/cgroup/%s/cpu.shares' % self.name, 'w')
+            cgroup.write('%s' % (int(cpu_share) * CPU_SHARE_MULT))
+            cgroup.close()
+        except:
+            pass
 
     def get_sched(self):
-        # have no way of querying scheduler right now on a per vserver basis
-        return (-1, False)
+        try:
+            cpu_share = int(int(self.config.get('cgroup/cpu.shares')) / CPU_SHARE_MULT)
+        except:
+            cpu_share = False
+        return (-1, cpu_share)
 
     def set_bwlimit(self, minrate = bwlimit.bwmin, maxrate = None,
                     exempt_min = None, exempt_max = None,
@@ -371,115 +379,35 @@ class VServer:
 
         return self.chroot_call(open, filename, mode, bufsize)
 
-    def __do_chcontext(self, state_file):
-
-        if state_file:
-            print >>state_file, "%u" % self.ctx
-            state_file.close()
-
-        if vserverimpl.chcontext(self.ctx, vserverimpl.text2bcaps(self.get_capabilities_config())):
-            self.set_resources()
-            vserverimpl.setup_done(self.ctx)
-
-    def __prep(self, runlevel, log):
-
-        """ 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
-        vserverimpl.setrunlevel(RUNDIR + "/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")
-
-    def __do_mount(self, *mount_args):
-
-        try:
-            vserverimpl.mount(*mount_args)
-        except OSError, ex:
-            if ex.errno == errno.EBUSY:
-                # assume already mounted
-                return
-            raise ex
-
     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:
-            # child process
+        subprocess.call("/usr/sbin/vserver %s enter" % self.name, shell=True)
+
+    # 2010 June 21 - Thierry 
+    # the slice initscript now gets invoked through rc - see sliver_vs.py in nodemanager
+    # and, rc is triggered as part of vserver .. start 
+    # so we don't have to worry about initscripts at all anymore here
+    def start(self, runlevel = 3):
+        if os.fork() != 0:
+            # Parent should just return.
+            self.vm_running = True
+            return
+        else:
+            os.setsid()
+            # first child process: fork again
+            if os.fork() != 0:
+                os._exit(0)    # Exit parent (the first child) of the second child.
+            # the grandson is the working one
+            os.chdir('/')
+            os.umask(0022)
             try:
-                # 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
-                fd = os.open("/dev/null", os.O_RDONLY)
-                if fd != 0:
-                    os.dup2(fd, 0)
-                    os.close(fd)
-                self.config.cache_it()
-                self.__do_chroot()
-                log = open("/var/log/boot.log", "a", 0)
-                if log.fileno() != 1:
-                    os.dup2(log.fileno(), 1)
-                os.dup2(1, 2)
-
-                print >>log, ("%s: starting the virtual server %s" %
-                              (time.asctime(time.gmtime()), self.name))
-
-                # perform pre-init cleanup
-                self.__prep(runlevel, log)
-
-                # 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,
-                                                   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)
-
-            # we get here due to an exception in the top-level child process
+                # start the vserver
+                subprocess.call(["/usr/sbin/vserver",self.name,"start"])
+
+            # we get here due to an exception in the grandson process
             except Exception, ex:
                 self.log(traceback.format_exc())
             os._exit(0)
 
-        # parent process
-        return child_pid
-
     def set_resources(self):
 
         """ Called when vserver context is entered for first time,
@@ -488,6 +416,13 @@ class VServer:
         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,
@@ -503,15 +438,19 @@ class VServer:
         (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)
         self.vm_running = False
-        self.rlimits_changed = False
+        subprocess.call("/usr/sbin/vserver %s stop" % self.name, shell=True)
+
+    def setname(self, slice_id):
+        pass
 
+    def getname(self):
+        '''Get vcVHI_CONTEXT field in kernel'''
+        return vserverimpl.getname(self.ctx)
 
 
 def create(vm_name, static = False, ctor = VServer):