add support for capabilities
[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         self.set_capabilities_config(self.rspec['capabilities'])
147         if self.rspec['capabilities']:
148             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
149
150         if False: # this code was commented out before
151             # N.B. net_*_rate are in kbps because of XML-RPC maxint
152             # limitations, convert to bps which is what bwlimit.py expects.
153             net_limits = (self.rspec['net_min_rate'] * 1000,
154                           self.rspec['net_max_rate'] * 1000,
155                           self.rspec['net_i2_min_rate'] * 1000,
156                           self.rspec['net_i2_max_rate'] * 1000,
157                           self.rspec['net_share'])
158             logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
159             logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
160             self.set_bwlimit(*net_limits)
161
162         cpu_min = self.rspec['cpu_min']
163         cpu_share = self.rspec['cpu_share']
164
165         if self.rspec['enabled'] > 0 and self.rspec['whitelist'] == 1:
166             if cpu_min >= 50:  # at least 5%: keep people from shooting themselves in the foot
167                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
168                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
169             else:
170                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
171                 self.set_sched_config(cpu_share, 0)
172
173             if False: # Does not work properly yet.
174                 if self.have_limits_changed():
175                     logger.log('%s: limits have changed --- restarting' % self.name)
176                     stopcount = 10
177                     while self.is_running() and stopcount > 0:
178                         self.stop()
179                         delay = 1
180                         time.sleep(delay)
181                         stopcount = stopcount - 1
182                     self.start()
183
184         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
185             logger.log('%s: disabling remote login' % self.name)
186             self.set_sched_config(0, 0)
187             self.stop()