remove setup parameter as no one uses it
[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 traceback
21 import os, os.path
22 import time
23
24 import vserver
25
26 import accounts
27 import logger
28 import tools
29 from threading import BoundedSemaphore
30
31 globalsem = BoundedSemaphore()
32
33 # special constant that tells vserver to keep its existing settings
34 KEEP_LIMIT = vserver.VC_LIM_KEEP
35
36 # populate the sliver/vserver specific default allocations table,
37 # which is used to look for slice attributes
38 DEFAULT_ALLOCATION = {}
39 for rlimit in vserver.RLIMITS.keys():
40     rlim = rlimit.lower()
41     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
42     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
43     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
44
45 class Sliver_VS(accounts.Account, vserver.VServer):
46     """This class wraps vserver.VServer to make its interface closer to what we need."""
47
48     SHELL = '/bin/vsh'
49     TYPE = 'sliver.VServer'
50     _init_disk_info_sem = globalsem
51
52     def __init__(self, rec):
53         logger.verbose ('initing Sliver_VS with name=%s'%rec['name'])
54         try:
55             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
56         except Exception, err:
57             if not isinstance(err, vserver.NoSuchVServer):
58                 # Probably a bad vserver or vserver configuration file
59                 logger.log_exc(self.name)
60                 logger.log('%s: recreating bad vserver' % rec['name'])
61                 self.destroy(rec['name'])
62             self.create(rec['name'], rec['vref'])
63             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
64
65         self.keys = ''
66         self.rspec = {}
67         self.initscript = ''
68         self.slice_id = rec['slice_id']
69         self.disk_usage_initialized = False
70         self.initscriptchanged = False
71         self.enabled = True
72         self.configure(rec)
73
74     @staticmethod
75     def create(name, vref = None):
76         logger.verbose('Sliver_VS:create - name=%s'%name)
77         if vref is None:
78             vref='default'
79         try:
80             ### locating the right slicefamily
81             # this is a first draft, and more a proof of concept thing
82             # the idea is to parse vref for dash-separated wishes,
83             # and to project these against the defaults
84             # so e.g. if the default slice family (as found in /etc/planetlab/slicefamily)
85             # is planetlab-f8-i386, then here is what we get
86             # vref=x86_64             -> vuseradd -t planetlab-f8-x86_64 
87             # vref=centos5            -> vuseradd -t planetlab-centos5-i386 
88             # vref=centos5-onelab     -> vuseradd -t onelab-centos5-i386 
89             # vref=planetflow         -> vuseradd -t planetflow-f8-i386
90             # vref=x86_64-planetflow  -> vuseradd -t planetflow-f8-x86_64
91
92             # default
93             default=file("/etc/planetlab/slicefamily").read().strip()
94             (pldistro,fcdistro,arch) = default.split("-")
95
96             known_archs = [ 'i386', 'x86_64' ]
97             known_fcdistros = [ 'centos5', 'f8', 'f9', 'f10', 'f11', 'f12' ]
98             # from the slice attribute: cut dashes and try to figure the meaning
99             slice_wishes = vref.split("-")
100             for wish in slice_wishes:
101                 if wish in known_archs:
102                     arch=wish
103                 elif wish in known_fcdistros:
104                     fcdistro=wish
105                 else:
106                     pldistro=wish
107
108             # rejoin the parts
109             refname="-".join( (pldistro,fcdistro,arch) )
110
111             # check the template exists -- there's probably a better way..
112             if not os.path.isdir ("/vservers/.vref/%s"%refname):
113                 logger.log("%s (%s) : vref %s not found, using default %s"%(
114                         name,vref,refname,default))
115                 refname=default
116                 # reset so arch is right
117                 (pldistro,fcdistro,arch) = default.split("-")
118                 # could check again, but as we have /etc/slicefamily 
119                 # there's probably no /vservers/.vref/default
120
121         except IOError:
122             # have not found slicefamily
123             logger.log("%s (%s): legacy node - using fallback vrefname 'default'"%(name,vref))
124             # for legacy nodes
125             refname="default"
126             arch="i386"
127         except:
128             logger.log("%s (%s) : unexpected error follows - using 'default'"%(name,vref))
129             logger.log(traceback.format_exc())
130             refname="default"
131             arch="i386"
132             
133         def personality (arch):
134             personality="linux32"
135             if arch.find("64")>=0:
136                 personality="linux64"
137             return personality
138
139         logger.log_call('/usr/sbin/vuseradd', '-t', refname, name)
140         # export slicename to the slice in /etc/slicename
141         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
142         # set personality: only if needed (if arch's differ)
143         if tools.root_context_arch() != arch:
144             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch))
145             logger.log('%s: set personality to %s'%(name,personality(arch)))
146
147     @staticmethod
148     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
149
150     def configure(self, rec):
151         new_rspec = rec['_rspec']
152         if new_rspec != self.rspec:
153             self.rspec = new_rspec
154             self.set_resources()
155
156         new_initscript = rec['initscript']
157         if new_initscript != self.initscript:
158             self.initscript = new_initscript
159             # not used anymore, we always check against the installed script
160             #self.initscriptchanged = True
161             self.refresh_slice_vinit()
162
163         accounts.Account.configure(self, rec)  # install ssh keys
164
165     # unconditionnally install and enable the generic vinit script
166     # mimicking chkconfig for enabling the generic vinit script
167     # this is hardwired for runlevel 3
168     def install_and_enable_vinit (self):
169         vinit_source="/usr/share/NodeManager/sliver-initscripts/vinit"
170         vinit_script="/vservers/%s/etc/rc.d/init.d/vinit"%self.name
171         rc3_link="/vservers/%s/etc/rc.d/rc3.d/S99vinit"%self.name
172         rc3_target="../init.d/vinit"
173         # install in sliver
174         body=file(vinit_source).read()
175         if tools.replace_file_with_string(vinit_script,body,chmod=0755):
176             logger.log("vsliver_vs: %s: installed generic vinit rc script"%self.name)
177         # create symlink for runlevel 3
178         if not os.path.islink(rc3_link):
179             try:
180                 logger.log("vsliver_vs: %s: creating runlevel3 symlink %s"%(self.name,rc3_link))
181                 os.symlink(rc3_target,rc3_link)
182             except:
183                 logger.log_exc("vsliver_vs: %s: failed to create runlevel3 symlink %s"%rc3_link)
184
185     # this one checks for the existence of the slice initscript
186     # install or remove the slice inistscript, as instructed by the initscript tag
187     def refresh_slice_vinit(self):
188         body=self.initscript
189         sliver_initscript="/vservers/%s/etc/rc.d/init.d/vinit.slice"%self.name
190         if tools.replace_file_with_string(sliver_initscript,body,remove_if_empty=True,chmod=0755):
191             if body:
192                 logger.log("vsliver_vs: %s: Installed new initscript in %s"%(self.name,sliver_initscript))
193             else:
194                 logger.log("vsliver_vs: %s: Removed obsolete initscript %s"%(self.name,sliver_initscript))
195
196     def start(self, delay=0):
197         if self.rspec['enabled'] <= 0:
198             logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
199         else:
200             logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
201             time.sleep(delay)
202             # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
203             self.install_and_enable_vinit()
204             # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
205             self.refresh_slice_vinit()
206             child_pid = os.fork()
207             if child_pid == 0:
208                 # VServer.start calls fork() internally,
209                 # so just close the nonstandard fds and fork once to avoid creating zombies
210                 tools.close_nonstandard_fds()
211                 vserver.VServer.start(self)
212                 os._exit(0)
213             else:
214                 os.waitpid(child_pid, 0)
215
216     def stop(self):
217         logger.log('%s: stopping' % self.name)
218         vserver.VServer.stop(self)
219
220     def is_running(self): 
221         return vserver.VServer.is_running(self)
222
223     def set_resources(self):
224         disk_max = self.rspec['disk_max']
225         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
226         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
227             if not self.disk_usage_initialized:
228                 self.vm_running = False
229                 Sliver_VS._init_disk_info_sem.acquire()
230                 logger.log('%s: computing disk usage: beginning' % self.name)
231                 try: self.init_disk_info()
232                 finally: Sliver_VS._init_disk_info_sem.release()
233                 logger.log('%s: computing disk usage: ended' % self.name)
234                 self.disk_usage_initialized = True
235             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
236         except:
237             logger.log('%s: failed to set max disk usage' % self.name)
238             logger.log_exc(self.name)
239
240         # get/set the min/soft/hard values for all of the vserver
241         # related RLIMITS.  Note that vserver currently only
242         # implements support for hard limits.
243         for limit in vserver.RLIMITS.keys():
244             type = limit.lower()
245             minimum  = self.rspec['%s_min'%type]
246             soft = self.rspec['%s_soft'%type]
247             hard = self.rspec['%s_hard'%type]
248             update = self.set_rlimit(limit, hard, soft, minimum)
249             if update:
250                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
251                            % (self.name, type, hard, soft, minimum))
252
253         self.set_capabilities_config(self.rspec['capabilities'])
254         if self.rspec['capabilities']:
255             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
256
257         cpu_pct = self.rspec['cpu_pct']
258         cpu_share = self.rspec['cpu_share']
259
260         count = 1
261         for key in self.rspec.keys():
262             if key.find('sysctl.') == 0:
263                 sysctl=key.split('.')
264                 try:
265                     # /etc/vservers/<guest>/sysctl/<id>/
266                     dirname = "/etc/vservers/%s/sysctl/%s" % (self.name, count)
267                     try:
268                         os.makedirs(dirname, 0755)
269                     except:
270                         pass
271                     setting = open("%s/setting" % dirname, "w")
272                     setting.write("%s\n" % key.lstrip("sysctl."))
273                     setting.close()
274                     value = open("%s/value" % dirname, "w")
275                     value.write("%s\n" % self.rspec[key])
276                     value.close()
277                     count += 1
278
279                     logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
280                 except IOError, e:
281                     logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
282                     logger.log("sliver_vs: %s: error = %s"%(self.name,e))
283
284
285         if self.rspec['enabled'] > 0:
286             if cpu_pct > 0:
287                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
288             else:
289                 cpu_pct = 0
290
291             if cpu_share > 0:
292                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
293             else:
294                 cpu_share = 0
295
296             self.set_sched_config(cpu_pct, cpu_share)
297             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
298             if self.rspec['ip_addresses'] != '0.0.0.0':
299                 logger.log('%s: setting IP address(es) to %s' % \
300                 (self.name, self.rspec['ip_addresses']))
301             self.set_ipaddresses_config(self.rspec['ip_addresses'])
302
303             try:
304                 vserver_config_path = '/etc/vservers/%s'%self.name
305                 if not os.path.exists (vserver_config_path):
306                     os.makedirs (vserver_config_path)
307                 file('%s/slice_id'%vserver_config_path, 'w').write("%d"%self.slice_id)
308                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
309             except IOError,e:
310                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
311             except Exception,e:
312                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
313
314             if self.enabled == False:
315                 self.enabled = True
316                 self.start()
317  
318             if False: # Does not work properly yet.
319                 if self.have_limits_changed():
320                     logger.log('%s: limits have changed --- restarting' % self.name)
321                     stopcount = 10
322                     while self.is_running() and stopcount > 0:
323                         self.stop()
324                         delay = 1
325                         time.sleep(delay)
326                         stopcount = stopcount - 1
327                     self.start()
328
329         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
330             logger.log('%s: disabling remote login' % self.name)
331             self.set_sched_config(0, 0)
332             self.enabled = False
333             self.stop()