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