Recording whether a node is whitelisted or not.
[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 # special constant that tells vserver to keep its existing settings
30 KEEP_LIMIT = vserver.VC_LIM_KEEP
31
32 class Sliver_VS(accounts.Account, vserver.VServer):
33     """This class wraps vserver.VServer to make its interface closer to what we need."""
34
35     SHELL = '/bin/vsh'
36     TYPE = 'sliver.VServer'
37     _init_disk_info_sem = threading.Semaphore(1)
38
39     def __init__(self, rec):
40         try:
41             vserver.VServer.__init__(self, rec['name'])
42         except Exception, err:
43             if not isinstance(err, vserver.NoSuchVServer):
44                 # Probably a bad vserver or vserver configuration file
45                 logger.log_exc()
46                 logger.log('%s: recreating bad vserver' % rec['name'])
47                 self.destroy(rec['name'])
48             self.create(rec['name'], rec['vref'])
49             vserver.VServer.__init__(self, rec['name'])
50
51         self.keys = ''
52         self.rspec = {}
53         self.initscript = ''
54         self.disk_usage_initialized = False
55         self.configure(rec)
56
57     @staticmethod
58     def create(name, vref = None):
59         if vref is not None:
60             logger.log_call('/usr/sbin/vuseradd', '-t', vref, name)
61         else:
62             logger.log_call('/usr/sbin/vuseradd', name)
63         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
64
65     @staticmethod
66     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
67
68     def configure(self, rec):
69         new_rspec = rec['_rspec']
70         if new_rspec != self.rspec:
71             self.rspec = new_rspec
72             self.set_resources()
73
74         new_initscript = rec['initscript']
75         if new_initscript != self.initscript:
76             self.initscript = new_initscript
77             logger.log('%s: installing initscript' % self.name)
78             def install_initscript():
79                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
80                 fd = os.open('/etc/rc.vinit', flags, 0755)
81                 os.write(fd, new_initscript)
82                 os.close(fd)
83             try: self.chroot_call(install_initscript)
84             except: logger.log_exc()
85
86         accounts.Account.configure(self, rec)  # install ssh keys
87
88     def start(self, delay=0):
89         if self.rspec['enabled'] > 0:
90             logger.log('%s: starting in %d seconds' % (self.name, delay))
91             time.sleep(delay)
92             child_pid = os.fork()
93             if child_pid == 0:
94                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
95                 tools.close_nonstandard_fds()
96                 vserver.VServer.start(self, True)
97                 os._exit(0)
98             else: os.waitpid(child_pid, 0)
99         else: logger.log('%s: not starting, is not enabled' % self.name)
100
101     def stop(self):
102         logger.log('%s: stopping' % self.name)
103         vserver.VServer.stop(self)
104
105     def set_resources(self):
106         disk_max = self.rspec['disk_max']
107         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
108         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
109             if not self.disk_usage_initialized:
110                 self.vm_running = False
111                 logger.log('%s: computing disk usage: beginning' % self.name)
112                 Sliver_VS._init_disk_info_sem.acquire()
113                 try: self.init_disk_info()
114                 finally: Sliver_VS._init_disk_info_sem.release()
115                 logger.log('%s: computing disk usage: ended' % self.name)
116                 self.disk_usage_initialized = True
117             vserver.VServer.set_disklimit(self, disk_max)
118         except OSError:
119             logger.log('%s: failed to set max disk usage' % self.name)
120             logger.log_exc()
121
122         # set min/soft/hard values for 'as', 'rss', 'nproc' and openfd
123         # Note that vserver currently only implements support for hard limits
124
125         as_min  = self.rspec['as_min']
126         as_soft = self.rspec['as_soft']
127         as_hard = self.rspec['as_hard']
128         self.set_AS_config(as_hard, as_soft, as_min)
129
130         rss_min  = self.rspec['rss_min']
131         rss_soft = self.rspec['rss_soft']
132         rss_hard = self.rspec['rss_hard']
133         self.set_RSS_config(rss_hard, rss_soft, rss_min)
134
135         nproc_min  = self.rspec['nproc_min']
136         nproc_soft = self.rspec['nproc_soft']
137         nproc_hard = self.rspec['nproc_hard']
138         self.set_NPROC_config(nproc_hard, nproc_soft, nproc_min)
139
140         openfd_min  = self.rspec['openfd_min']
141         openfd_soft = self.rspec['openfd_soft']
142         openfd_hard = self.rspec['openfd_hard']
143         self.set_OPENFD_config(openfd_hard, openfd_soft, openfd_min)
144
145         self.set_WHITELISTED_config(self.rspec['whitelist'])
146
147         if False: # this code was commented out before
148             # N.B. net_*_rate are in kbps because of XML-RPC maxint
149             # limitations, convert to bps which is what bwlimit.py expects.
150             net_limits = (self.rspec['net_min_rate'] * 1000,
151                           self.rspec['net_max_rate'] * 1000,
152                           self.rspec['net_i2_min_rate'] * 1000,
153                           self.rspec['net_i2_max_rate'] * 1000,
154                           self.rspec['net_share'])
155             logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
156             logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
157             self.set_bwlimit(*net_limits)
158
159         cpu_min = self.rspec['cpu_min']
160         cpu_share = self.rspec['cpu_share']
161
162         if self.rspec['enabled'] > 0 and self.rspec['whitelist'] == 1:
163             if cpu_min >= 50:  # at least 5%: keep people from shooting themselves in the foot
164                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
165                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
166             else:
167                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
168                 self.set_sched_config(cpu_share, 0)
169         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
170             logger.log('%s: disabling remote login' % self.name)
171             self.set_sched_config(0, 0)
172             self.stop()