Add more info to tracebacks. Echos slice name before traceback.
[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         try:
49             vserver.VServer.__init__(self, rec['name'])
50         except Exception, err:
51             if not isinstance(err, vserver.NoSuchVServer):
52                 # Probably a bad vserver or vserver configuration file
53                 logger.log_exc(self.name)
54                 logger.log('%s: recreating bad vserver' % rec['name'])
55                 self.destroy(rec['name'])
56             self.create(rec['name'], rec['vref'])
57             vserver.VServer.__init__(self, rec['name'])
58
59         self.keys = ''
60         self.rspec = {}
61         self.initscript = ''
62         self.disk_usage_initialized = False
63         self.initscriptchanged = 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:
93                 self.chroot_call(install_initscript)
94                 self.initscriptchanged = True
95             except: logger.log_exc(self.name)
96
97         accounts.Account.configure(self, rec)  # install ssh keys
98
99     def start(self, delay=0):
100         if self.rspec['enabled'] > 0:
101             logger.log('%s: starting in %d seconds' % (self.name, delay))
102             time.sleep(delay)
103             child_pid = os.fork()
104             if child_pid == 0:
105                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
106                 tools.close_nonstandard_fds()
107                 vserver.VServer.start(self, True)
108                 os._exit(0)
109             else: os.waitpid(child_pid, 0)
110         else: logger.log('%s: not starting, is not enabled' % self.name)
111         self.initscriptchanged = False
112
113     def stop(self):
114         logger.log('%s: stopping' % self.name)
115         vserver.VServer.stop(self)
116
117     def set_resources(self):
118         disk_max = self.rspec['disk_max']
119         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
120         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
121             if not self.disk_usage_initialized:
122                 self.vm_running = False
123                 logger.log('%s: computing disk usage: beginning' % self.name)
124                 Sliver_VS._init_disk_info_sem.acquire()
125                 try: self.init_disk_info()
126                 finally: Sliver_VS._init_disk_info_sem.release()
127                 logger.log('%s: computing disk usage: ended' % self.name)
128                 self.disk_usage_initialized = True
129             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
130         except:
131             logger.log('%s: failed to set max disk usage' % self.name)
132             logger.log_exc(self.name)
133
134         # get/set the min/soft/hard values for all of the vserver
135         # related RLIMITS.  Note that vserver currently only
136         # implements support for hard limits.
137         for limit in vserver.RLIMITS.keys():
138             type = limit.lower()
139             minimum  = self.rspec['%s_min'%type]
140             soft = self.rspec['%s_soft'%type]
141             hard = self.rspec['%s_hard'%type]
142             self.set_rlimit_config(limit, hard, soft, minimum)
143
144         self.set_capabilities_config(self.rspec['capabilities'])
145         if self.rspec['capabilities']:
146             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
147
148         if False: # this code was commented out before
149             # N.B. net_*_rate are in kbps because of XML-RPC maxint
150             # limitations, convert to bps which is what bwlimit.py expects.
151             net_limits = (self.rspec['net_min_rate'] * 1000,
152                           self.rspec['net_max_rate'] * 1000,
153                           self.rspec['net_i2_min_rate'] * 1000,
154                           self.rspec['net_i2_max_rate'] * 1000,
155                           self.rspec['net_share'])
156             logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
157             logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
158             self.set_bwlimit(*net_limits)
159
160         cpu_min = self.rspec['cpu_min']
161         cpu_share = self.rspec['cpu_share']
162
163         if self.rspec['enabled'] > 0:
164             if cpu_min >= 50:  # at least 5%: keep people from shooting themselves in the foot
165                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
166                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
167             else:
168                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
169                 self.set_sched_config(cpu_share, 0)
170
171             if self.rspec['ip_addresses'] != '0.0.0.0':
172                 logger.log('%s: setting IP address(es) to %s' % (self.name, self.rspec['ip_addresses']))
173             self.set_ipaddresses_config(self.rspec['ip_addresses'])
174
175             if False: # Does not work properly yet.
176                 if self.have_limits_changed():
177                     logger.log('%s: limits have changed --- restarting' % self.name)
178                     stopcount = 10
179                     while self.is_running() and stopcount > 0:
180                         self.stop()
181                         delay = 1
182                         time.sleep(delay)
183                         stopcount = stopcount - 1
184                     self.start()
185
186         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
187             logger.log('%s: disabling remote login' % self.name)
188             self.set_sched_config(0, 0)
189             self.stop()