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