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