provide default to WITH_INIT and WITH_SYSTEMD right in the makefile
[nodemanager.git] / sliver_vs.py
1 #
2
3 """VServer slivers.
4
5 There are a couple of tricky things going on here.  First, the kernel
6 needs disk usage information in order to enforce the quota.  However,
7 determining disk usage redundantly strains the disks.  Thus, the
8 Sliver_VS.disk_usage_initialized flag is used to determine whether
9 this initialization has been made.
10
11 Second, it's not currently possible to set the scheduler parameters
12 for a sliver unless that sliver has a running process.  /bin/vsh helps
13 us out by reading the configuration file so that it can set the
14 appropriate limits after entering the sliver context.  Making the
15 syscall that actually sets the parameters gives a harmless error if no
16 process is running.  Thus we keep vm_running on when setting scheduler
17 parameters so that set_sched_params() always makes the syscall, and we
18 don't have to guess if there is a running process or not.
19 """
20
21 import errno
22 import traceback
23 import os, os.path
24 import sys
25 import time
26 from threading import BoundedSemaphore
27 import subprocess
28
29 # the util-vserver-pl module
30 import vserver
31
32 import logger
33 import tools
34 from account import Account
35 from initscript import Initscript
36
37 # special constant that tells vserver to keep its existing settings
38 KEEP_LIMIT = vserver.VC_LIM_KEEP
39
40 # populate the sliver/vserver specific default allocations table,
41 # which is used to look for slice attributes
42 DEFAULT_ALLOCATION = {}
43 for rlimit in vserver.RLIMITS.keys():
44     rlim = rlimit.lower()
45     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
46     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
47     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
48
49 class Sliver_VS(vserver.VServer, Account, Initscript):
50     """This class wraps vserver.VServer to make its interface closer to what we need."""
51
52     SHELL = '/bin/vsh'
53     TYPE = 'sliver.VServer'
54     _init_disk_info_sem = BoundedSemaphore()
55
56     def __init__(self, rec):
57         name=rec['name']
58         logger.verbose ('sliver_vs: %s init'%name)
59         try:
60             logger.log("sliver_vs: %s: first chance..."%name)
61             vserver.VServer.__init__(self, name,logfile='/var/log/nodemanager')
62             Account.__init__ (self, name)
63             Initscript.__init__ (self, name)
64         except Exception, err:
65             if not isinstance(err, vserver.NoSuchVServer):
66                 # Probably a bad vserver or vserver configuration file
67                 logger.log_exc("sliver_vs:__init__ (first chance) %s",name=name)
68                 logger.log('sliver_vs: %s: recreating bad vserver' % name)
69                 self.destroy(name)
70             self.create(name, rec)
71             vserver.VServer.__init__(self, name,logfile='/var/log/nodemanager')
72             Account.__init__ (self, name)
73             Initscript.__init__ (self, name)
74
75         self.rspec = {}
76         self.slice_id = rec['slice_id']
77         self.disk_usage_initialized = False
78         self.enabled = True
79         # xxx this almost certainly is wrong...
80         self.configure(rec)
81
82     @staticmethod
83     def create(name, rec = None):
84         logger.verbose('sliver_vs: %s: create'%name)
85         vref = rec['vref']
86         if vref is None:
87             logger.log("sliver_vs: %s: ERROR - no vref attached, this is unexpected"%(name))
88             # added by caglar
89             # band-aid for short period as old API doesn't have GetSliceFamily function
90             #return
91             vref = "planetlab-f8-i386"
92
93         # used to look in /etc/planetlab/family,
94         # now relies on the 'GetSliceFamily' extra attribute in GetSlivers()
95         # which for legacy is still exposed here as the 'vref' key
96
97         # check the template exists -- there's probably a better way..
98         if not os.path.isdir ("/vservers/.vref/%s"%vref):
99             logger.log ("sliver_vs: %s: ERROR Could not create sliver - vreference image %s not found"%(name,vref))
100             return
101
102         # guess arch
103         try:
104             (x,y,arch)=vref.split('-')
105         # mh, this of course applies when 'vref' is e.g. 'netflow'
106         # and that's not quite right
107         except:
108             arch='i386'
109
110         def personality (arch):
111             personality="linux32"
112             if arch.find("64")>=0:
113                 personality="linux64"
114             return personality
115
116         command=[]
117         # be verbose
118         command += ['/bin/bash','-x',]
119         command += ['/usr/sbin/vuseradd', ]
120         if 'attributes' in rec and 'isolate_loopback' in rec['attributes'] and rec['attributes']['isolate_loopback'] == '1':
121             command += [ "-i",]
122         # the vsliver imge to use
123         command += [ '-t', vref, ]
124         # slice name
125         command += [ name, ]            
126         logger.log_call(command, timeout=15*60)
127         # export slicename to the slice in /etc/slicename
128         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
129         file('/vservers/%s/etc/slicefamily' % name, 'w').write(vref)
130         # set personality: only if needed (if arch's differ)
131         if tools.root_context_arch() != arch:
132             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch)+"\n")
133             logger.log('sliver_vs: %s: set personality to %s'%(name,personality(arch)))
134
135     @staticmethod
136     def destroy(name):
137         # need to umount before we trash, otherwise we end up with sequels in 
138         # /vservers/slicename/ (namely in home/ )
139         # also because this is a static method we cannot check for 'omf_control'
140         # but it is no big deal as umount_ssh_dir checks before it umounts..
141         Account.umount_ssh_dir(name)
142         logger.log("sliver_vs: destroying %s"%name)
143         logger.log_call(['/bin/bash','-x','/usr/sbin/vuserdel', name, ])
144
145
146     def configure(self, rec):
147         # in case we update nodemanager..
148         self.install_and_enable_vinit()
149
150         new_rspec = rec['_rspec']
151         if new_rspec != self.rspec:
152             self.rspec = new_rspec
153             self.set_resources()
154
155         # do the configure part from Initscript
156         Initscript.configure(self,rec)
157
158         Account.configure(self, rec)  # install ssh keys
159
160     def start(self, delay=0):
161         if self.rspec['enabled'] <= 0:
162             logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
163             return
164         logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
165         time.sleep(delay)
166         # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
167         self.install_and_enable_vinit()
168         # expose .ssh for omf_friendly slivers
169         if 'omf_control' in self.rspec['tags']:
170             Account.mount_ssh_dir(self.name)
171         # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
172         self.refresh_slice_vinit()
173         child_pid = os.fork()
174         if child_pid == 0:
175             # VServer.start calls fork() internally,
176             # so just close the nonstandard fds and fork once to avoid creating zombies
177             tools.close_nonstandard_fds()
178             vserver.VServer.start(self)
179             os._exit(0)
180         else:
181             os.waitpid(child_pid, 0)
182
183     def stop(self):
184         logger.log('sliver_vs: %s: stopping' % self.name)
185         vserver.VServer.stop(self)
186
187     def is_running(self):
188         return vserver.VServer.is_running(self)
189
190     # this one seems to belong in Initscript at first sight, 
191     # but actually depends on the underlying vm techno
192     # so let's keep it here
193     def rerun_slice_vinit(self):
194         command = "/usr/sbin/vserver %s exec /etc/rc.d/init.d/vinit restart" % (self.name)
195         logger.log("vsliver_vs: %s: Rerunning slice initscript: %s" % (self.name, command))
196         subprocess.call(command + "&", stdin=open('/dev/null', 'r'), stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT, shell=True)
197
198     def set_resources(self):
199         disk_max = self.rspec['disk_max']
200         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
201         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
202             if not self.disk_usage_initialized:
203                 self.vm_running = False
204                 Sliver_VS._init_disk_info_sem.acquire()
205                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
206                 # init_disk_info is inherited from VServer
207                 try: self.init_disk_info()
208                 finally: Sliver_VS._init_disk_info_sem.release()
209                 logger.log('sliver_vs: %s: computing disk usage: ended' % self.name)
210                 self.disk_usage_initialized = True
211             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
212         except:
213             logger.log_exc('sliver_vs: failed to set max disk usage',name=self.name)
214
215         # get/set the min/soft/hard values for all of the vserver
216         # related RLIMITS.  Note that vserver currently only
217         # implements support for hard limits.
218         for limit in vserver.RLIMITS.keys():
219             type = limit.lower()
220             minimum  = self.rspec['%s_min'%type]
221             soft = self.rspec['%s_soft'%type]
222             hard = self.rspec['%s_hard'%type]
223             update = self.set_rlimit(limit, hard, soft, minimum)
224             if update:
225                 logger.log('sliver_vs: %s: setting rlimit %s to (%d, %d, %d)'
226                            % (self.name, type, hard, soft, minimum))
227
228         self.set_capabilities_config(self.rspec['capabilities'])
229         if self.rspec['capabilities']:
230             logger.log('sliver_vs: %s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
231
232         cpu_pct = self.rspec['cpu_pct']
233         cpu_share = self.rspec['cpu_share']
234
235         count = 1
236         for key in self.rspec.keys():
237             if key.find('sysctl.') == 0:
238                 sysctl=key.split('.')
239                 try:
240                     # /etc/vservers/<guest>/sysctl/<id>/
241                     dirname = "/etc/vservers/%s/sysctl/%s" % (self.name, count)
242                     try:
243                         os.makedirs(dirname, 0755)
244                     except:
245                         pass
246                     setting = open("%s/setting" % dirname, "w")
247                     setting.write("%s\n" % key.lstrip("sysctl."))
248                     setting.close()
249                     value = open("%s/value" % dirname, "w")
250                     value.write("%s\n" % self.rspec[key])
251                     value.close()
252                     count += 1
253
254                     logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
255                 except IOError, e:
256                     logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
257                     logger.log("sliver_vs: %s: error = %s"%(self.name,e))
258
259
260         if self.rspec['enabled'] > 0:
261             if cpu_pct > 0:
262                 logger.log('sliver_vs: %s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
263             else:
264                 cpu_pct = 0
265
266             if cpu_share > 0:
267                 logger.log('sliver_vs: %s: setting cpu share to %d' % (self.name, cpu_share))
268             else:
269                 cpu_share = 0
270
271             self.set_sched_config(cpu_pct, cpu_share)
272             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
273             if self.rspec['ip_addresses'] != '0.0.0.0':
274                 logger.log('sliver_vs: %s: setting IP address(es) to %s' % \
275                 (self.name, self.rspec['ip_addresses']))
276             add_loopback = True
277             if 'isolate_loopback' in self.rspec['tags']:
278                 add_loopback = self.rspec['tags']['isolate_loopback'] != "1"
279             self.set_ipaddresses_config(self.rspec['ip_addresses'], add_loopback)
280
281             #logger.log("sliver_vs: %s: Setting name to %s" % (self.name, self.slice_id))
282             #self.setname(self.slice_id)
283             #logger.log("sliver_vs: %s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
284             try:
285                 vserver_config_path = '/etc/vservers/%s'%self.name
286                 if not os.path.exists (vserver_config_path):
287                     os.makedirs (vserver_config_path)
288                 file('%s/slice_id'%vserver_config_path, 'w').write("%d\n"%self.slice_id)
289                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
290             except IOError,e:
291                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
292             except Exception,e:
293                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
294
295
296             if self.enabled == False:
297                 self.enabled = True
298                 self.start()
299
300             if False: # Does not work properly yet.
301                 if self.have_limits_changed():
302                     logger.log('sliver_vs: %s: limits have changed --- restarting' % self.name)
303                     stopcount = 10
304                     while self.is_running() and stopcount > 0:
305                         self.stop()
306                         delay = 1
307                         time.sleep(delay)
308                         stopcount = stopcount - 1
309                     self.start()
310
311         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
312             logger.log('sliver_vs: %s: disabling remote login' % self.name)
313             self.set_sched_config(0, 0)
314             self.enabled = False
315             self.stop()