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