Refactoring in progress...
[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 vserver
22
23 from bwlimit import bwmin, bwmax
24 import accounts
25 import logger
26 import tools
27
28
29 DEFAULTS = {'disk_max': 5000000,
30             'net_min':    bwmin,
31             'net_max':    bwmax,
32             'net2_min':   bwmin,
33             'net2_max':   bwmax,
34             'net_share':      1,
35             'enabled':        1,
36             'cpu_min':        0,
37             'cpu_share':     32,
38             'keys':          '',
39             'initscript':    ''}
40
41 class Sliver_VS(vserver.VServer):
42     """This class wraps vserver.VServer to make its interface closer to what we need for the Node Manager."""
43
44     SHELL = '/bin/vsh'
45     TYPE = 'sliver.VServer'
46
47     def __init__(self, name):
48         vserver.VServer.__init__(self, name)
49         self.current_keys = ''
50         self.current_initscript = ''
51         self.disk_usage_initialized = False
52         self.rec = DEFAULTS.copy()
53
54
55     @staticmethod
56     def create(name): logger.log_call('/usr/sbin/vuseradd', name)
57
58     @staticmethod
59     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
60
61
62     def configure(self, rec):
63         self.rec = DEFAULTS.copy()
64         self.rec.update(rec)
65
66         self.set_resources()
67
68         new_keys = self.rec['keys']
69         if new_keys != self.current_keys:
70             accounts.install_keys(rec)
71             self.current_keys = new_keys
72
73         new_initscript = self.rec['initscript']
74         if new_initscript != self.current_initscript:
75             logger.log('%s: installing initscript' % self.name)
76             def install_initscript():
77                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
78                 fd = os.open('/etc/rc.vinit', flags, 0755)
79                 os.write(fd, new_initscript)
80                 os.close(fd)
81             try: self.chroot_call(install_initscript)
82             except: logger.log_exc()
83             self.current_initscript = new_initscript
84
85
86     def start(self):
87         if self.rec['enabled']:
88             logger.log('%s: starting' % self.name)
89             child_pid = os.fork()
90             if child_pid == 0:
91                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
92                 tools.close_nonstandard_fds()
93                 vserver.VServer.start(self, True)
94                 os._exit(0)
95             else: os.waitpid(child_pid, 0)
96         else: logger.log('%s: not starting, is not enabled' % self.name)
97
98     def stop(self):
99         logger.log('%s: stopping' % self.name)
100         vserver.VServer.stop(self)
101
102
103     def set_resources(self):
104         disk_max = int(self.rec['disk_max'])
105         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
106         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
107             if not self.disk_usage_initialized:
108                 self.vm_running = False
109                 logger.log('%s: computing disk usage' % self.name)
110                 self.init_disk_info()
111                 self.disk_usage_initialized = True
112             vserver.VServer.set_disklimit(self, disk_max_KiB)
113         except OSError: logger.log_exc()
114
115         net_limits = (int(self.rec['net_min']),
116                       int(self.rec['net_max']),
117                       int(self.rec['net2_min']),
118                       int(self.rec['net2_max']),
119                       int(self.rec['net_share']))
120         logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
121         logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
122         self.set_bwlimit(*net_limits)
123
124         cpu_min = int(self.rec['cpu_min'])
125         cpu_share = int(self.rec['cpu_share'])
126         if bool(self.rec['enabled']):
127             if cpu_min > 0:
128                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
129                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
130             else:
131                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
132                 self.set_sched_config(cpu_share, 0)
133         else:
134             # 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()