Added log message to code that records the slice id.
[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
27 import vserver
28
29 import accounts
30 import logger
31 import tools
32 from threading import BoundedSemaphore
33
34 globalsem = BoundedSemaphore()
35
36 # special constant that tells vserver to keep its existing settings
37 KEEP_LIMIT = vserver.VC_LIM_KEEP
38
39 # populate the sliver/vserver specific default allocations table,
40 # which is used to look for slice attributes
41 DEFAULT_ALLOCATION = {}
42 for rlimit in vserver.RLIMITS.keys():
43     rlim = rlimit.lower()
44     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
45     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
46     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
47
48 class Sliver_VS(accounts.Account, vserver.VServer):
49     """This class wraps vserver.VServer to make its interface closer to what we need."""
50
51     SHELL = '/bin/vsh'
52     TYPE = 'sliver.VServer'
53     _init_disk_info_sem = globalsem
54
55     def __init__(self, rec):
56         name=rec['name']
57         logger.verbose ('sliver_vs: %s init'%name)
58         try:
59             logger.log("sliver_vs: %s: first chance..."%name)
60             vserver.VServer.__init__(self, name,logfile='/var/log/nm')
61         except Exception, err:
62             if not isinstance(err, vserver.NoSuchVServer):
63                 # Probably a bad vserver or vserver configuration file
64                 logger.log_exc("sliver_vs:__init__ (first chance) %s",name=name)
65                 logger.log('sliver_vs: %s: recreating bad vserver' % name)
66                 self.destroy(name)
67             self.create(name, rec['vref'])
68             logger.log("sliver_vs: %s: second chance..."%name)
69             vserver.VServer.__init__(self, name,logfile='/var/log/nm')
70
71         self.keys = ''
72         self.rspec = {}
73         self.initscript = ''
74         self.slice_id = rec['slice_id']
75         self.disk_usage_initialized = False
76         self.initscriptchanged = False
77         self.enabled = True
78         self.configure(rec)
79
80     @staticmethod
81     def create(name, vref = None):
82         logger.verbose('sliver_vs: %s: create'%name)
83         if vref is None:
84             logger.log("sliver_vs: %s: ERROR - no vref attached, this is unexpected"%(name))
85             return
86         # used to look in /etc/planetlab/family, 
87         # now relies on the 'GetSliceFamily' extra attribute in GetSlivers()
88         # which for legacy is still exposed here as the 'vref' key
89         
90         # check the template exists -- there's probably a better way..
91         if not os.path.isdir ("/vservers/.vref/%s"%vref):
92             logger.log ("sliver_vs: %s: ERROR Could not create sliver - vreference image %s not found"%(name,vref))
93             return
94
95         # guess arch
96         try:
97             (x,y,arch)=vref.split('-')
98         # mh, this of course applies when 'vref' is e.g. 'netflow'
99         # and that's not quite right
100         except:
101             arch='i386'
102             
103         def personality (arch):
104             personality="linux32"
105             if arch.find("64")>=0:
106                 personality="linux64"
107             return personality
108
109 #        logger.log_call(['/usr/sbin/vuseradd', '-t', vref, name, ], timeout=15*60)
110         logger.log_call(['/bin/bash','-x','/usr/sbin/vuseradd', '-t', vref, name, ], timeout=15*60)
111         # export slicename to the slice in /etc/slicename
112         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
113         file('/vservers/%s/etc/slicefamily' % name, 'w').write(vref)
114         # set personality: only if needed (if arch's differ)
115         if tools.root_context_arch() != arch:
116             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch))
117             logger.log('sliver_vs: %s: set personality to %s'%(name,personality(arch)))
118
119     @staticmethod
120     def destroy(name): 
121 #        logger.log_call(['/usr/sbin/vuserdel', name, ])
122         logger.log_call(['/bin/bash','-x','/usr/sbin/vuserdel', name, ])
123
124     def configure(self, rec):
125         new_rspec = rec['_rspec']
126         if new_rspec != self.rspec:
127             self.rspec = new_rspec
128             self.set_resources()
129
130         new_initscript = rec['initscript']
131         if new_initscript != self.initscript:
132             self.initscript = new_initscript
133             self.initscriptchanged = True
134
135         accounts.Account.configure(self, rec)  # install ssh keys
136
137     def start(self, delay=0):
138         if self.rspec['enabled'] > 0:
139             logger.log('sliver_vs: %s: starting in %d seconds' % (self.name, delay))
140             time.sleep(delay)
141             # VServer.start calls fork() internally, 
142             # so just close the nonstandard fds and fork once to avoid creating zombies
143             child_pid = os.fork()
144             if child_pid == 0:
145                 if self.initscriptchanged:
146                     logger.log('sliver_vs: %s: installing initscript' % self.name)
147                     def install_initscript():
148                         flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
149                         fd = os.open('/etc/rc.vinit', flags, 0755)
150                         os.write(fd, self.initscript)
151                         os.close(fd)
152                     try:
153                         self.chroot_call(install_initscript)
154                     except: logger.log_exc("sliver_vs: start",name=self.name)
155                 tools.close_nonstandard_fds()
156                 vserver.VServer.start(self)
157                 os._exit(0)
158             else: 
159                 os.waitpid(child_pid, 0)
160                 self.initscriptchanged = False
161         else: logger.log('sliver_vs: not starting %s, is not enabled'%self.name)
162
163     def stop(self):
164         logger.log('sliver_vs: %s: stopping' % self.name)
165         vserver.VServer.stop(self)
166
167     def is_running(self): 
168         return vserver.VServer.is_running(self)
169
170     def set_resources(self,setup=False):
171         disk_max = self.rspec['disk_max']
172         logger.log('sliver_vs: %s: setting max disk usage to %d KiB' % (self.name, disk_max))
173         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
174             if not self.disk_usage_initialized:
175                 self.vm_running = False
176                 Sliver_VS._init_disk_info_sem.acquire()
177                 logger.log('sliver_vs: %s: computing disk usage: beginning' % self.name)
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"%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                 
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()