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