(*) basically no operational change
[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'])
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'])
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 >= 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 self.rspec['ip_addresses'] != '0.0.0.0':
174                 logger.log('%s: setting IP address(es) to %s' % (self.name, self.rspec['ip_addresses']))
175             self.set_ipaddresses_config(self.rspec['ip_addresses'])
176
177             if False: # Does not work properly yet.
178                 if self.have_limits_changed():
179                     logger.log('%s: limits have changed --- restarting' % self.name)
180                     stopcount = 10
181                     while self.is_running() and stopcount > 0:
182                         self.stop()
183                         delay = 1
184                         time.sleep(delay)
185                         stopcount = stopcount - 1
186                     self.start()
187
188         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
189             logger.log('%s: disabling remote login' % self.name)
190             self.set_sched_config(0, 0)
191             self.stop()