*** empty log message ***
[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         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
170             if False: # Does not work properly yet.
171                 if self.have_limits_changed():
172                     logger.log('%s: limits have changed --- restarting' % self.name)
173                     stopcount = 10
174                     while self.is_running() and stopcount > 0:
175                         self.stop()
176                         delay = 1
177                         time.sleep(delay)
178                         stopcount = stopcount - 1
179                     self.start()
180
181         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
182             logger.log('%s: disabling remote login' % self.name)
183             self.set_sched_config(0, 0)
184             self.stop()