cosmetic
[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 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.initscript = ''
72         self.slice_id = rec['slice_id']
73         self.disk_usage_initialized = False
74         self.initscriptchanged = False
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             self.initscriptchanged = True
132
133         accounts.Account.configure(self, rec)  # install ssh keys
134
135     def start(self, delay=0):
136         if self.rspec['enabled'] > 0:
137             logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
138             time.sleep(delay)
139             # VServer.start calls fork() internally, 
140             # so just close the nonstandard fds and fork once to avoid creating zombies
141             child_pid = os.fork()
142             if child_pid == 0:
143                 if self.initscriptchanged:
144                     logger.log('sliver_vs: %s: installing initscript' % self.name)
145                     def install_initscript():
146                         flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
147                         fd = os.open('/etc/rc.vinit', flags, 0755)
148                         os.write(fd, self.initscript)
149                         os.close(fd)
150                     try:
151                         self.chroot_call(install_initscript)
152                     except: logger.log_exc("sliver_vs: start",name=self.name)
153                 tools.close_nonstandard_fds()
154                 vserver.VServer.start(self)
155                 os._exit(0)
156             else: 
157                 os.waitpid(child_pid, 0)
158                 self.initscriptchanged = False
159         else: logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
160
161     def stop(self):
162         logger.log('sliver_vs: %s: stopping' % self.name)
163         vserver.VServer.stop(self)
164
165     def is_running(self): 
166         return vserver.VServer.is_running(self)
167
168     def set_resources(self,setup=False):
169         disk_max = self.rspec['disk_max']
170         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
171         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
172             if not self.disk_usage_initialized:
173                 self.vm_running = False
174                 Sliver_VS._init_disk_info_sem.acquire()
175                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
176                 try: self.init_disk_info()
177                 finally: Sliver_VS._init_disk_info_sem.release()
178                 logger.log('sliver_vs: %s: computing disk usage: ended' % self.name)
179                 self.disk_usage_initialized = True
180             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
181         except:
182             logger.log_exc('sliver_vs: failed to set max disk usage',name=self.name)
183
184         # get/set the min/soft/hard values for all of the vserver
185         # related RLIMITS.  Note that vserver currently only
186         # implements support for hard limits.
187         for limit in vserver.RLIMITS.keys():
188             type = limit.lower()
189             minimum  = self.rspec['%s_min'%type]
190             soft = self.rspec['%s_soft'%type]
191             hard = self.rspec['%s_hard'%type]
192             update = self.set_rlimit(limit, hard, soft, minimum)
193             if update:
194                 logger.log('sliver_vs: %s: setting rlimit %s to (%d, %d, %d)'
195                            % (self.name, type, hard, soft, minimum))
196
197         self.set_capabilities_config(self.rspec['capabilities'])
198         if self.rspec['capabilities']:
199             logger.log('sliver_vs: %s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
200
201         cpu_pct = self.rspec['cpu_pct']
202         cpu_share = self.rspec['cpu_share']
203
204         if setup:
205             for key in self.rspec.keys():
206                 if key.find('sysctl.') == 0:
207                     sysctl=key.split('.')
208                     try:
209                         path="/proc/sys/%s" % ("/".join(sysctl[1:]))
210                         logger.log("sliver_vs: %s: opening %s"%(self.name,path))
211                         flags = os.O_WRONLY
212                         fd = os.open(path, flags)
213                         logger.log("sliver_vs: %s: writing %s=%s"%(self.name,key,self.rspec[key]))
214                         os.write(fd,self.rspec[key])
215                         os.close(fd)
216                     except IOError, e:
217                         logger.log("sliver_vs: %s: could not set %s=%s"%(self.name,key,self.rspec[key]))
218                         logger.log("sliver_vs: %s: error = %s"%(self.name,e))
219
220
221         if self.rspec['enabled'] > 0:
222             if cpu_pct > 0:
223                 logger.log('sliver_vs: %s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
224             else:
225                 cpu_pct = 0
226
227             if cpu_share > 0:
228                 logger.log('sliver_vs: %s: setting cpu share to %d' % (self.name, cpu_share))
229             else:
230                 cpu_share = 0
231
232             self.set_sched_config(cpu_pct, cpu_share)
233             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
234             if self.rspec['ip_addresses'] != '0.0.0.0':
235                 logger.log('sliver_vs: %s: setting IP address(es) to %s' % \
236                 (self.name, self.rspec['ip_addresses']))
237             self.set_ipaddresses_config(self.rspec['ip_addresses'])
238
239             #logger.log("sliver_vs: %s: Setting name to %s" % (self.name, self.slice_id)) 
240             #self.setname(self.slice_id) 
241             #logger.log("sliver_vs: %s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
242             try:
243                 vserver_config_path = '/etc/vservers/%s'%self.name
244                 if not os.path.exists (vserver_config_path):
245                     os.makedirs (vserver_config_path)
246                 file('%s/slice_id'%vserver_config_path, 'w').write("%d\n"%self.slice_id)
247                 logger.log("sliver_vs: Recorded slice id %d for slice %s"%(self.slice_id,self.name))
248             except IOError,e:
249                 logger.log("sliver_vs: Could not record slice_id for slice %s. Error: %s"%(self.name,str(e)))
250             except Exception,e:
251                 logger.log_exc("sliver_vs: Error recording slice id: %s"%str(e),name=self.name)
252                 
253
254             if self.enabled == False:
255                 self.enabled = True
256                 self.start()
257  
258             if False: # Does not work properly yet.
259                 if self.have_limits_changed():
260                     logger.log('sliver_vs: %s: limits have changed --- restarting' % self.name)
261                     stopcount = 10
262                     while self.is_running() and stopcount > 0:
263                         self.stop()
264                         delay = 1
265                         time.sleep(delay)
266                         stopcount = stopcount - 1
267                     self.start()
268
269         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
270             logger.log('sliver_vs: %s: disabling remote login' % self.name)
271             self.set_sched_config(0, 0)
272             self.enabled = False
273             self.stop()