start to use /etc/vservers/<guest>/sysctl/<id>/{setting,value} files as new kernels...
[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.slice_id = rec['slice_id']
73         self.disk_usage_initialized = False
74         self.initscript = ''
75         self.enabled = True
76         self.configure(rec)
77
78     @staticmethod
79     def create(name, vref = None):
80         logger.verbose('sliver_vs: %s: create'%name)
81         if vref is None:
82             logger.log("sliver_vs: %s: ERROR - no vref attached, this is unexpected"%(name))
83             # added by caglar
84             # band-aid for short period as old API doesn't have GetSliceFamily function
85             #return
86             vref = "planetlab-f8-i386"
87
88         # used to look in /etc/planetlab/family,
89         # now relies on the 'GetSliceFamily' extra attribute in GetSlivers()
90         # which for legacy is still exposed here as the 'vref' key
91
92         # check the template exists -- there's probably a better way..
93         if not os.path.isdir ("/vservers/.vref/%s"%vref):
94             logger.log ("sliver_vs: %s: ERROR Could not create sliver - vreference image %s not found"%(name,vref))
95             return
96
97         # guess arch
98         try:
99             (x,y,arch)=vref.split('-')
100         # mh, this of course applies when 'vref' is e.g. 'netflow'
101         # and that's not quite right
102         except:
103             arch='i386'
104
105         def personality (arch):
106             personality="linux32"
107             if arch.find("64")>=0:
108                 personality="linux64"
109             return personality
110
111 #        logger.log_call(['/usr/sbin/vuseradd', '-t', vref, name, ], timeout=15*60)
112         logger.log_call(['/bin/bash','-x','/usr/sbin/vuseradd', '-t', vref, name, ], timeout=15*60)
113         # export slicename to the slice in /etc/slicename
114         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
115         file('/vservers/%s/etc/slicefamily' % name, 'w').write(vref)
116         # set personality: only if needed (if arch's differ)
117         if tools.root_context_arch() != arch:
118             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch)+"\n")
119             logger.log('sliver_vs: %s: set personality to %s'%(name,personality(arch)))
120
121     @staticmethod
122     def destroy(name):
123 #        logger.log_call(['/usr/sbin/vuserdel', name, ])
124         logger.log_call(['/bin/bash','-x','/usr/sbin/vuserdel', name, ])
125
126     def configure(self, rec):
127         new_rspec = rec['_rspec']
128         if new_rspec != self.rspec:
129             self.rspec = new_rspec
130             self.set_resources()
131
132         new_initscript = rec['initscript']
133         if new_initscript != self.initscript:
134             self.initscript = new_initscript
135             # not used anymore, we always check against the installed script
136             #self.initscriptchanged = True
137             self.refresh_slice_vinit()
138
139         accounts.Account.configure(self, rec)  # install ssh keys
140
141     # unconditionnally install and enable the generic vinit script
142     # mimicking chkconfig for enabling the generic vinit script
143     # this is hardwired for runlevel 3
144     def install_and_enable_vinit (self):
145         vinit_source="/usr/share/NodeManager/sliver-initscripts/vinit"
146         vinit_script="/vservers/%s/etc/rc.d/init.d/vinit"%self.name
147         rc3_link="/vservers/%s/etc/rc.d/rc3.d/S99vinit"%self.name
148         rc3_target="../init.d/vinit"
149         # install in sliver
150         body=file(vinit_source).read()
151         if tools.replace_file_with_string(vinit_script,body,chmod=0755):
152             logger.log("vsliver_vs: %s: installed generic vinit rc 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: creating runlevel3 symlink %s"%(self.name,rc3_link))
157                 os.symlink(rc3_target,rc3_link)
158             except:
159                 logger.log_exc("vsliver_vs: %s: failed to create runlevel3 symlink %s"%rc3_link)
160
161     # this one checks for the existence of the slice initscript
162     # install or remove the slice inistscript, as instructed by the initscript tag
163     def refresh_slice_vinit(self):
164         body=self.initscript
165         sliver_initscript="/vservers/%s/etc/rc.d/init.d/vinit.slice"%self.name
166         if tools.replace_file_with_string(sliver_initscript,body,remove_if_empty=True,chmod=0755):
167             if body:
168                 logger.log("vsliver_vs: %s: Installed new initscript in %s"%(self.name,sliver_initscript))
169             else:
170                 logger.log("vsliver_vs: %s: Removed obsolete initscript %s"%(self.name,sliver_initscript))
171
172     def start(self, delay=0):
173         if self.rspec['enabled'] <= 0:
174             logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
175         else:
176             logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
177             time.sleep(delay)
178             # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
179             self.install_and_enable_vinit()
180             # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
181             self.refresh_slice_vinit()
182             child_pid = os.fork()
183             if child_pid == 0:
184                 # VServer.start calls fork() internally,
185                 # so just close the nonstandard fds and fork once to avoid creating zombies
186                 tools.close_nonstandard_fds()
187                 vserver.VServer.start(self)
188                 os._exit(0)
189             else:
190                 os.waitpid(child_pid, 0)
191
192     def stop(self):
193         logger.log('sliver_vs: %s: stopping' % self.name)
194         vserver.VServer.stop(self)
195
196     def is_running(self):
197         return vserver.VServer.is_running(self)
198
199     def set_resources(self,setup=False):
200         disk_max = self.rspec['disk_max']
201         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
202         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
203             if not self.disk_usage_initialized:
204                 self.vm_running = False
205                 Sliver_VS._init_disk_info_sem.acquire()
206                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
207                 # init_disk_info is inherited from VServer
208                 try: self.init_disk_info()
209                 finally: Sliver_VS._init_disk_info_sem.release()
210                 logger.log('sliver_vs: %s: computing disk usage: ended' % self.name)
211                 self.disk_usage_initialized = True
212             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
213         except:
214             logger.log_exc('sliver_vs: failed to set max disk usage',name=self.name)
215
216         # get/set the min/soft/hard values for all of the vserver
217         # related RLIMITS.  Note that vserver currently only
218         # implements support for hard limits.
219         for limit in vserver.RLIMITS.keys():
220             type = limit.lower()
221             minimum  = self.rspec['%s_min'%type]
222             soft = self.rspec['%s_soft'%type]
223             hard = self.rspec['%s_hard'%type]
224             update = self.set_rlimit(limit, hard, soft, minimum)
225             if update:
226                 logger.log('sliver_vs: %s: setting rlimit %s to (%d, %d, %d)'
227                            % (self.name, type, hard, soft, minimum))
228
229         self.set_capabilities_config(self.rspec['capabilities'])
230         if self.rspec['capabilities']:
231             logger.log('sliver_vs: %s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
232
233         cpu_pct = self.rspec['cpu_pct']
234         cpu_share = self.rspec['cpu_share']
235
236         if setup:
237             count = 1
238             for key in self.rspec.keys():
239                 if key.find('sysctl.') == 0:
240                     sysctl=key.split('.')
241                     try:
242                         # /etc/vservers/<guest>/sysctl/<id>/
243                         dirname = "/etc/vservers/%s/sysctl/%s" % (self.name, count)
244                         try:
245                             os.makedirs(dirname, 0755)
246                         except:
247                             pass
248                         setting = open("%s/setting" % dirname, "w")
249                         setting.write("%s\n" % key.lstrip("sysctl."))
250                         setting.close()
251                         value = open("%s/value" % dirname, "w")
252                         value.write("%s\n" % self.rspec[key])
253                         value.close()
254                         count += 1
255
256                         logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
257                     except IOError, e:
258                         logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
259                         logger.log("sliver_vs: %s: error = %s"%(self.name,e))
260
261
262         if self.rspec['enabled'] > 0:
263             if cpu_pct > 0:
264                 logger.log('sliver_vs: %s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
265             else:
266                 cpu_pct = 0
267
268             if cpu_share > 0:
269                 logger.log('sliver_vs: %s: setting cpu share to %d' % (self.name, cpu_share))
270             else:
271                 cpu_share = 0
272
273             self.set_sched_config(cpu_pct, cpu_share)
274             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
275             if self.rspec['ip_addresses'] != '0.0.0.0':
276                 logger.log('sliver_vs: %s: setting IP address(es) to %s' % \
277                 (self.name, self.rspec['ip_addresses']))
278             self.set_ipaddresses_config(self.rspec['ip_addresses'])
279
280             #logger.log("sliver_vs: %s: Setting name to %s" % (self.name, self.slice_id))
281             #self.setname(self.slice_id)
282             #logger.log("sliver_vs: %s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
283             try:
284                 vserver_config_path = '/etc/vservers/%s'%self.name
285                 if not os.path.exists (vserver_config_path):
286                     os.makedirs (vserver_config_path)
287                 file('%s/slice_id'%vserver_config_path, 'w').write("%d\n"%self.slice_id)
288                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
289             except IOError,e:
290                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
291             except Exception,e:
292                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
293
294
295             if self.enabled == False:
296                 self.enabled = True
297                 self.start()
298
299             if False: # Does not work properly yet.
300                 if self.have_limits_changed():
301                     logger.log('sliver_vs: %s: limits have changed --- restarting' % self.name)
302                     stopcount = 10
303                     while self.is_running() and stopcount > 0:
304                         self.stop()
305                         delay = 1
306                         time.sleep(delay)
307                         stopcount = stopcount - 1
308                     self.start()
309
310         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
311             logger.log('sliver_vs: %s: disabling remote login' % self.name)
312             self.set_sched_config(0, 0)
313             self.enabled = False
314             self.stop()