merge changes from HEAD
[nodemanager.git] / sliver_vs.py
1 """VServer slivers.
2
3 There are a couple of tricky things going on here.  First, the kernel
4 needs disk usage information in order to enforce the quota.  However,
5 determining disk usage redundantly strains the disks.  Thus, the
6 Sliver_VS.disk_usage_initialized flag is used to determine whether
7 this initialization has been made.
8
9 Second, it's not currently possible to set the scheduler parameters
10 for a sliver unless that sliver has a running process.  /bin/vsh helps
11 us out by reading the configuration file so that it can set the
12 appropriate limits after entering the sliver context.  Making the
13 syscall that actually sets the parameters gives a harmless error if no
14 process is running.  Thus we keep vm_running on when setting scheduler
15 parameters so that set_sched_params() always makes the syscall, and we
16 don't have to guess if there is a running process or not.
17 """
18
19 import errno
20 import os
21 import threading
22 import time
23 import vserver
24
25 import accounts
26 import logger
27 import tools
28
29
30 class Sliver_VS(accounts.Account, vserver.VServer):
31     """This class wraps vserver.VServer to make its interface closer to what we need."""
32
33     SHELL = '/bin/vsh'
34     TYPE = 'sliver.VServer'
35     _init_disk_info_sem = threading.Semaphore(1)
36
37     def __init__(self, rec):
38         try:
39             vserver.VServer.__init__(self, rec['name'])
40         except vserver.NoSuchVServer:
41             self.create(rec['name'], rec['vref'])
42             vserver.VServer.__init__(self, rec['name'])
43
44         self.keys = ''
45         self.rspec = {}
46         self.initscript = ''
47         self.disk_usage_initialized = False
48         self.configure(rec)
49
50     @staticmethod
51     def create(name, vref = None):
52         if vref is not None:
53             logger.log_call('/usr/sbin/vuseradd', '-t', vref, name)
54         else:
55             logger.log_call('/usr/sbin/vuseradd', name)
56
57     @staticmethod
58     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
59
60     def configure(self, rec):
61         new_rspec = rec['_rspec']
62         if new_rspec != self.rspec:
63             self.rspec = new_rspec
64             self.set_resources()
65
66         new_initscript = rec['initscript']
67         if new_initscript != self.initscript:
68             self.initscript = new_initscript
69             logger.log('%s: installing initscript' % self.name)
70             def install_initscript():
71                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
72                 fd = os.open('/etc/rc.vinit', flags, 0755)
73                 os.write(fd, new_initscript)
74                 os.close(fd)
75             try: self.chroot_call(install_initscript)
76             except: logger.log_exc()
77
78         accounts.Account.configure(self, rec)  # install ssh keys
79
80     def start(self, delay=0):
81         if self.rspec['enabled']:
82             logger.log('%s: starting in %d seconds' % (self.name, delay))
83             child_pid = os.fork()
84             if child_pid == 0:
85                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
86                 tools.close_nonstandard_fds()
87                 time.sleep(delay)
88                 vserver.VServer.start(self, True)
89                 os._exit(0)
90             else: os.waitpid(child_pid, 0)
91         else: logger.log('%s: not starting, is not enabled' % self.name)
92
93     def stop(self):
94         logger.log('%s: stopping' % self.name)
95         vserver.VServer.stop(self)
96
97     def set_resources(self):
98         disk_max = self.rspec['disk_max']
99         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
100         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
101             if not self.disk_usage_initialized:
102                 self.vm_running = False
103                 logger.log('%s: computing disk usage: beginning' % self.name)
104                 Sliver_VS._init_disk_info_sem.acquire()
105                 try: self.init_disk_info()
106                 finally: Sliver_VS._init_disk_info_sem.release()
107                 logger.log('%s: computing disk usage: ended' % self.name)
108                 self.disk_usage_initialized = True
109             vserver.VServer.set_disklimit(self, disk_max)
110         except OSError:
111             logger.log('%s: failed to set max disk usage' % self.name)
112             logger.log_exc()
113
114         # N.B. net_*_rate are in kbps because of XML-RPC maxint
115         # limitations, convert to bps which is what bwlimit.py expects.
116         net_limits = (self.rspec['net_min_rate'] * 1000,
117                       self.rspec['net_max_rate'] * 1000,
118                       self.rspec['net_i2_min_rate'] * 1000,
119                       self.rspec['net_i2_max_rate'] * 1000,
120                       self.rspec['net_share'])
121         logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
122         logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
123         self.set_bwlimit(*net_limits)
124
125         cpu_min = self.rspec['cpu_min']
126         cpu_share = self.rspec['cpu_share']
127         if self.rspec['enabled'] > 0:
128             if cpu_min >= 50:  # at least 5%: keep people from shooting themselves in the foot
129                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
130                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
131             else:
132                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
133                 self.set_sched_config(cpu_share, 0)
134         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
135             logger.log('%s: disabling remote login' % self.name)
136             self.set_sched_config(0, 0)
137             self.stop()