...
[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 import accounts
24 import logger
25 import tools
26
27
28 DEFAULTS = {'disk_max': 5000000,
29             'net_min':    bwmin,
30             'net_max':    bwmax,
31             'net2_min':   bwmin,
32             'net2_max':   bwmax,
33             'net_share':      1,
34             'enabled':        1,
35             'cpu_min':        0,
36             'cpu_share':     32,
37             'keys':          '',
38             'initscript':    ''}
39
40 class Sliver_VS(accounts.Account, vserver.VServer):
41     """This class wraps vserver.VServer to make its interface closer to what we need for the Node Manager."""
42
43     SHELL = '/bin/vsh'
44     TYPE = 'sliver.VServer'
45
46     def __init__(self, rec):
47         vserver.VServer.__init__(self, rec['name'])
48         self.keys = ''
49         self.rspec = {}
50         self.initscript = ''
51         self.disk_usage_initialized = False
52         self.configure(rec)
53
54     @staticmethod
55     def create(name): 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):
81         if self.rspec['enabled']:
82             logger.log('%s: starting' % self.name)
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                 vserver.VServer.start(self, True)
88                 os._exit(0)
89             else: os.waitpid(child_pid, 0)
90         else: logger.log('%s: not starting, is not enabled' % self.name)
91
92     def stop(self):
93         logger.log('%s: stopping' % self.name)
94         vserver.VServer.stop(self)
95
96     def set_resources(self):
97         disk_max = self.rspec['disk_max']
98         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
99         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
100             if not self.disk_usage_initialized:
101                 self.vm_running = False
102                 logger.log('%s: computing disk usage' % self.name)
103                 self.init_disk_info()
104                 self.disk_usage_initialized = True
105             vserver.VServer.set_disklimit(self, disk_max_KiB)
106         except OSError: logger.log_exc()
107
108         net_limits = (self.rspec['net_min'], self.rspec['net_max'], self.rspec['net2_min'], self.rspec['net2_max'], self.rspec['net_share'])
109         logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
110         logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
111         self.set_bwlimit(*net_limits)
112
113         cpu_min = self.rspec['cpu_min']
114         cpu_share = self.rspec['cpu_share']
115         if self.rspec['enabled']:
116             if cpu_min > 0:
117                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
118                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
119             else:
120                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
121                 self.set_sched_config(cpu_share, 0)
122         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
123             logger.log('%s: disabling remote login' % self.name)
124             self.set_sched_config(0, 0)
125             self.stop()