Change default CPU share from 32 to 1
[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 time
22 import vserver
23
24 import accounts
25 import logger
26 import tools
27
28 # special constant that tells vserver to keep its existing settings
29 KEEP_LIMIT = vserver.VC_LIM_KEEP
30
31 # populate the sliver/vserver specific default allocations table,
32 # which is used to look for slice attributes
33 DEFAULT_ALLOCATION = {}
34 for rlimit in vserver.RLIMITS.keys():
35     rlim = rlimit.lower()
36     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
37     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
38     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
39
40 class Sliver_VS(accounts.Account, vserver.VServer):
41     """This class wraps vserver.VServer to make its interface closer to what we need."""
42
43     SHELL = '/bin/vsh'
44     TYPE = 'sliver.VServer'
45     _init_disk_info_sem = tools.NMLock("/var/run/nm-disk-info.lock")
46
47     def __init__(self, rec):
48         logger.verbose ('initing Sliver_VS with name=%s'%rec['name'])
49         try:
50             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
51         except Exception, err:
52             if not isinstance(err, vserver.NoSuchVServer):
53                 # Probably a bad vserver or vserver configuration file
54                 logger.log_exc(self.name)
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'],logfile='/var/log/nm')
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         logger.verbose('Sliver_VS:create - name=%s'%name)
70         if vref is not None:
71             logger.log_call('/usr/sbin/vuseradd', '-t', vref, name)
72         else:
73             logger.log_call('/usr/sbin/vuseradd', name)
74         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
75
76     @staticmethod
77     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
78
79     def configure(self, rec):
80         new_rspec = rec['_rspec']
81         if new_rspec != self.rspec:
82             self.rspec = new_rspec
83             self.set_resources()
84
85         new_initscript = rec['initscript']
86         if new_initscript != self.initscript:
87             self.initscript = new_initscript
88             logger.log('%s: installing initscript' % self.name)
89             def install_initscript():
90                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
91                 fd = os.open('/etc/rc.vinit', flags, 0755)
92                 os.write(fd, new_initscript)
93                 os.close(fd)
94             try:
95                 self.chroot_call(install_initscript)
96                 self.initscriptchanged = True
97             except: logger.log_exc(self.name)
98
99         accounts.Account.configure(self, rec)  # install ssh keys
100
101     def start(self, delay=0):
102         if self.rspec['enabled'] > 0:
103             logger.log('%s: starting in %d seconds' % (self.name, delay))
104             time.sleep(delay)
105             child_pid = os.fork()
106             if child_pid == 0:
107                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
108                 tools.close_nonstandard_fds()
109                 vserver.VServer.start(self, True)
110                 os._exit(0)
111             else: os.waitpid(child_pid, 0)
112         else: logger.log('%s: not starting, is not enabled' % self.name)
113         self.initscriptchanged = False
114
115     def stop(self):
116         logger.log('%s: stopping' % self.name)
117         vserver.VServer.stop(self)
118
119     def set_resources(self):
120         disk_max = self.rspec['disk_max']
121         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
122         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
123             if not self.disk_usage_initialized:
124                 self.vm_running = False
125                 logger.log('%s: computing disk usage: beginning' % self.name)
126                 Sliver_VS._init_disk_info_sem.acquire()
127                 try: self.init_disk_info()
128                 finally: Sliver_VS._init_disk_info_sem.release()
129                 logger.log('%s: computing disk usage: ended' % self.name)
130                 self.disk_usage_initialized = True
131             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
132         except:
133             logger.log('%s: failed to set max disk usage' % self.name)
134             logger.log_exc(self.name)
135
136         # get/set the min/soft/hard values for all of the vserver
137         # related RLIMITS.  Note that vserver currently only
138         # implements support for hard limits.
139         for limit in vserver.RLIMITS.keys():
140             type = limit.lower()
141             minimum  = self.rspec['%s_min'%type]
142             soft = self.rspec['%s_soft'%type]
143             hard = self.rspec['%s_hard'%type]
144             self.set_rlimit_config(limit, hard, soft, minimum)
145
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:
166             if cpu_min > 0:
167                 logger.log('%s: setting cpu to %d%% guaranteed' % (self.name, cpu_min))
168             else:
169                 cpu_min = 0
170
171             if cpu_share > 0:
172                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
173             else:
174                 cpu_share = 0
175
176             self.set_sched_config(cpu_min, cpu_share)
177             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
178             if self.rspec['ip_addresses'] != '0.0.0.0':
179                 logger.log('%s: setting IP address(es) to %s' % (self.name, self.rspec['ip_addresses']))
180             self.set_ipaddresses_config(self.rspec['ip_addresses'])
181
182             if False: # Does not work properly yet.
183                 if self.have_limits_changed():
184                     logger.log('%s: limits have changed --- restarting' % self.name)
185                     stopcount = 10
186                     while self.is_running() and stopcount > 0:
187                         self.stop()
188                         delay = 1
189                         time.sleep(delay)
190                         stopcount = stopcount - 1
191                     self.start()
192
193         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
194             logger.log('%s: disabling remote login' % self.name)
195             self.set_sched_config(0, 0)
196             self.stop()