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