fix merge between onelab and princeton repositories
[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             # band-aid for short period as old API doesn't have GetSliceFamily function
84             vref = "planetlab-f8-i386"
85             #return
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     def start(self, delay=0):
172         if self.rspec['enabled'] <= 0:
173             logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
174         else:
175             logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
176             time.sleep(delay)
177             # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
178             self.install_and_enable_vinit()
179             # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
180             self.refresh_slice_vinit()
181             child_pid = os.fork()
182             if child_pid == 0:
183                 # VServer.start calls fork() internally,
184                 # so just close the nonstandard fds and fork once to avoid creating zombies
185                 tools.close_nonstandard_fds()
186                 vserver.VServer.start(self)
187                 os._exit(0)
188             else:
189                 os.waitpid(child_pid, 0)
190
191     def stop(self):
192         logger.log('sliver_vs: %s: stopping' % self.name)
193         vserver.VServer.stop(self)
194
195     def is_running(self):
196         return vserver.VServer.is_running(self)
197
198     def set_resources(self,setup=False):
199         disk_max = self.rspec['disk_max']
200         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
201         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
202             if not self.disk_usage_initialized:
203                 self.vm_running = False
204                 Sliver_VS._init_disk_info_sem.acquire()
205                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
206                 # init_disk_info is inherited from VServer
207                 try: self.init_disk_info()
208                 finally: Sliver_VS._init_disk_info_sem.release()
209                 logger.log('sliver_vs: %s: computing disk usage: ended' % self.name)
210                 self.disk_usage_initialized = True
211             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
212         except:
213             logger.log_exc('sliver_vs: failed to set max disk usage',name=self.name)
214
215         # get/set the min/soft/hard values for all of the vserver
216         # related RLIMITS.  Note that vserver currently only
217         # implements support for hard limits.
218         for limit in vserver.RLIMITS.keys():
219             type = limit.lower()
220             minimum  = self.rspec['%s_min'%type]
221             soft = self.rspec['%s_soft'%type]
222             hard = self.rspec['%s_hard'%type]
223             update = self.set_rlimit(limit, hard, soft, minimum)
224             if update:
225                 logger.log('sliver_vs: %s: setting rlimit %s to (%d, %d, %d)'
226                            % (self.name, type, hard, soft, minimum))
227
228         self.set_capabilities_config(self.rspec['capabilities'])
229         if self.rspec['capabilities']:
230             logger.log('sliver_vs: %s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
231
232         cpu_pct = self.rspec['cpu_pct']
233         cpu_share = self.rspec['cpu_share']
234
235         if setup:
236             for key in self.rspec.keys():
237                 if key.find('sysctl.') == 0:
238                     sysctl=key.split('.')
239                     try:
240                         path="/proc/sys/%s" % ("/".join(sysctl[1:]))
241                         logger.log("sliver_vs: %s: opening %s"%(self.name,path))
242                         flags = os.O_WRONLY
243                         fd = os.open(path, flags)
244                         logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
245                         os.write(fd,self.rspec[key])
246                         os.close(fd)
247                     except IOError, e:
248                         logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
249                         logger.log("sliver_vs: %s: error = %s"%(self.name,e))
250
251
252         if self.rspec['enabled'] > 0:
253             if cpu_pct > 0:
254                 logger.log('sliver_vs: %s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
255             else:
256                 cpu_pct = 0
257
258             if cpu_share > 0:
259                 logger.log('sliver_vs: %s: setting cpu share to %d' % (self.name, cpu_share))
260             else:
261                 cpu_share = 0
262
263             self.set_sched_config(cpu_pct, cpu_share)
264             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
265             if self.rspec['ip_addresses'] != '0.0.0.0':
266                 logger.log('sliver_vs: %s: setting IP address(es) to %s' % \
267                 (self.name, self.rspec['ip_addresses']))
268             self.set_ipaddresses_config(self.rspec['ip_addresses'])
269
270             #logger.log("sliver_vs: %s: Setting name to %s" % (self.name, self.slice_id))
271             #self.setname(self.slice_id)
272             #logger.log("sliver_vs: %s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
273             try:
274                 vserver_config_path = '/etc/vservers/%s'%self.name
275                 if not os.path.exists (vserver_config_path):
276                     os.makedirs (vserver_config_path)
277                 file('%s/slice_id'%vserver_config_path, 'w').write("%d\n"%self.slice_id)
278                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
279             except IOError,e:
280                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
281             except Exception,e:
282                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
283
284
285             if self.enabled == False:
286                 self.enabled = True
287                 self.start()
288
289             if False: # Does not work properly yet.
290                 if self.have_limits_changed():
291                     logger.log('sliver_vs: %s: limits have changed --- restarting' % self.name)
292                     stopcount = 10
293                     while self.is_running() and stopcount > 0:
294                         self.stop()
295                         delay = 1
296                         time.sleep(delay)
297                         stopcount = stopcount - 1
298                     self.start()
299
300         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
301             logger.log('sliver_vs: %s: disabling remote login' % self.name)
302             self.set_sched_config(0, 0)
303             self.enabled = False
304             self.stop()