an attempt to see if avoiding to run initscripts altogether solves the slice re-creat...
[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             # added by caglar
88             # band-aid for short period as old API doesn't have GetSliceFamily function
89             vref = "planetlab-f8-i386"
90             logger.log("sliver_vs: %s: ERROR - no vref attached, using hard-wired default %s"%(name,vref))
91
92         # used to look in /etc/planetlab/family,
93         # now relies on the 'GetSliceFamily' extra attribute in GetSlivers()
94         # which for legacy is still exposed here as the 'vref' key
95
96         # check the template exists -- there's probably a better way..
97         if not os.path.isdir ("/vservers/.vref/%s"%vref):
98             logger.log ("sliver_vs: %s: ERROR Could not create sliver - vreference image %s not found"%(name,vref))
99             return
100
101         # compute guest personality
102         try:
103             (x,y,arch)=vref.split('-')
104         # mh, this of course applies when 'vref' is e.g. 'netflow'
105         # and that's not quite right
106         except:
107             arch='i386'
108
109         def personality (arch): return "linux64" if arch.find("64") >=0 else "linux32"
110
111         command=[]
112         # be verbose
113         command += ['/bin/bash','-x',]
114         command += ['/usr/sbin/vuseradd', ]
115         if 'attributes' in rec and 'isolate_loopback' in rec['attributes'] and rec['attributes']['isolate_loopback'] == '1':
116             command += [ "-i",]
117         # the vsliver imge to use
118         command += [ '-t', vref, ]
119         # slice name
120         command += [ name, ]            
121         logger.log_call(command, timeout=15*60)
122         # export slicename to the slice in /etc/slicename
123         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
124         file('/vservers/%s/etc/slicefamily' % name, 'w').write(vref)
125         # set personality: only if needed (if arch's differ)
126         if tools.root_context_arch() != arch:
127             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch)+"\n")
128             logger.log('sliver_vs: %s: set personality to %s'%(name,personality(arch)))
129
130     @staticmethod
131     def destroy(name):
132         # need to umount before we trash, otherwise we end up with sequels in 
133         # /vservers/slicename/ (namely in home/ )
134         # also because this is a static method we cannot check for 'omf_control'
135         # but it is no big deal as umount_ssh_dir checks before it umounts..
136         Account.umount_ssh_dir(name)
137         logger.log("sliver_vs: destroying %s"%name)
138         logger.log_call(['/bin/bash','-x','/usr/sbin/vuserdel', name, ])
139
140
141     def configure(self, rec):
142         # in case we update nodemanager..
143         self.install_and_enable_vinit()
144
145         new_rspec = rec['_rspec']
146         if new_rspec != self.rspec:
147             self.rspec = new_rspec
148             self.set_resources()
149
150         # do the configure part from Initscript
151         # i.e. install slice initscript if defined
152         Initscript.configure(self, rec)
153         # install ssh keys
154         Account.configure(self, rec)
155
156     # remember configure() always gets called *before* start()
157     # in particular the slice initscript
158     # is expected to be in place already at this point
159     def start(self, delay=0):
160         if self.rspec['enabled'] <= 0:
161             logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
162             return
163         logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
164         time.sleep(delay)
165         # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
166         self.install_and_enable_vinit()
167         # expose .ssh for omf_friendly slivers
168         if 'omf_control' in self.rspec['tags']:
169             Account.mount_ssh_dir(self.name)
170         child_pid = os.fork()
171         if child_pid == 0:
172             # VServer.start calls fork() internally,
173             # so just close the nonstandard fds and fork once to avoid creating zombies
174             tools.close_nonstandard_fds()
175             vserver.VServer.start(self)
176             os._exit(0)
177         else:
178             os.waitpid(child_pid, 0)
179
180     def stop(self):
181         logger.log('sliver_vs: %s: stopping' % self.name)
182         vserver.VServer.stop(self)
183
184     def is_running(self):
185         return vserver.VServer.is_running(self)
186
187     # this one seems to belong in Initscript at first sight, 
188     # but actually depends on the underlying vm techno
189     # so let's keep it here
190     def rerun_slice_vinit(self):
191         command = "/usr/sbin/vserver %s exec /etc/rc.d/init.d/vinit restart" % (self.name)
192         logger.log("vsliver_vs: %s: Rerunning slice initscript: %s" % (self.name, command))
193         subprocess.call(command + "&", stdin=open('/dev/null', 'r'), stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT, shell=True)
194
195     def set_resources(self):
196         disk_max = self.rspec['disk_max']
197         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
198         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
199             if not self.disk_usage_initialized:
200                 self.vm_running = False
201                 Sliver_VS._init_disk_info_sem.acquire()
202                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
203                 # init_disk_info is inherited from VServer
204                 try: self.init_disk_info()
205                 finally: Sliver_VS._init_disk_info_sem.release()
206                 logger.log('sliver_vs: %s: computing disk usage: ended' % self.name)
207                 self.disk_usage_initialized = True
208             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
209         except:
210             logger.log_exc('sliver_vs: failed to set max disk usage',name=self.name)
211
212         # get/set the min/soft/hard values for all of the vserver
213         # related RLIMITS.  Note that vserver currently only
214         # implements support for hard limits.
215         for limit in vserver.RLIMITS.keys():
216             type = limit.lower()
217             minimum  = self.rspec['%s_min'%type]
218             soft = self.rspec['%s_soft'%type]
219             hard = self.rspec['%s_hard'%type]
220             update = self.set_rlimit(limit, hard, soft, minimum)
221             if update:
222                 logger.log('sliver_vs: %s: setting rlimit %s to (%d, %d, %d)'
223                            % (self.name, type, hard, soft, minimum))
224
225         self.set_capabilities_config(self.rspec['capabilities'])
226         if self.rspec['capabilities']:
227             logger.log('sliver_vs: %s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
228
229         cpu_pct = self.rspec['cpu_pct']
230         cpu_share = self.rspec['cpu_share']
231
232         count = 1
233         for key in self.rspec.keys():
234             if key.find('sysctl.') == 0:
235                 sysctl=key.split('.')
236                 try:
237                     # /etc/vservers/<guest>/sysctl/<id>/
238                     dirname = "/etc/vservers/%s/sysctl/%s" % (self.name, count)
239                     try:
240                         os.makedirs(dirname, 0755)
241                     except:
242                         pass
243                     setting = open("%s/setting" % dirname, "w")
244                     setting.write("%s\n" % key.lstrip("sysctl."))
245                     setting.close()
246                     value = open("%s/value" % dirname, "w")
247                     value.write("%s\n" % self.rspec[key])
248                     value.close()
249                     count += 1
250
251                     logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
252                 except IOError, e:
253                     logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
254                     logger.log("sliver_vs: %s: error = %s"%(self.name,e))
255
256
257         if self.rspec['enabled'] > 0:
258             if cpu_pct > 0:
259                 logger.log('sliver_vs: %s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
260             else:
261                 cpu_pct = 0
262
263             if cpu_share > 0:
264                 logger.log('sliver_vs: %s: setting cpu share to %d' % (self.name, cpu_share))
265             else:
266                 cpu_share = 0
267
268             self.set_sched_config(cpu_pct, cpu_share)
269             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
270             if self.rspec['ip_addresses'] != '0.0.0.0':
271                 logger.log('sliver_vs: %s: setting IP address(es) to %s' % \
272                 (self.name, self.rspec['ip_addresses']))
273             add_loopback = True
274             if 'isolate_loopback' in self.rspec['tags']:
275                 add_loopback = self.rspec['tags']['isolate_loopback'] != "1"
276             self.set_ipaddresses_config(self.rspec['ip_addresses'], add_loopback)
277
278             #logger.log("sliver_vs: %s: Setting name to %s" % (self.name, self.slice_id))
279             #self.setname(self.slice_id)
280             #logger.log("sliver_vs: %s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
281             try:
282                 vserver_config_path = '/etc/vservers/%s'%self.name
283                 if not os.path.exists (vserver_config_path):
284                     os.makedirs (vserver_config_path)
285                 file('%s/slice_id'%vserver_config_path, 'w').write("%d\n"%self.slice_id)
286                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
287             except IOError,e:
288                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
289             except Exception,e:
290                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
291
292
293             if self.enabled == False:
294                 self.enabled = True
295                 self.start()
296
297             if False: # Does not work properly yet.
298                 if self.have_limits_changed():
299                     logger.log('sliver_vs: %s: limits have changed --- restarting' % self.name)
300                     stopcount = 10
301                     while self.is_running() and stopcount > 0:
302                         self.stop()
303                         delay = 1
304                         time.sleep(delay)
305                         stopcount = stopcount - 1
306                     self.start()
307
308         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
309             logger.log('sliver_vs: %s: disabling remote login' % self.name)
310             self.set_sched_config(0, 0)
311             self.enabled = False
312             self.stop()