undo broken change
[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         logger.verbose ('initing Sliver_VS with name=%s'%rec['name'])
57         try:
58             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
59         except Exception, err:
60             if not isinstance(err, vserver.NoSuchVServer):
61                 # Probably a bad vserver or vserver configuration file
62                 logger.log_exc(self.name)
63                 logger.log('%s: recreating bad vserver' % rec['name'])
64                 self.destroy(rec['name'])
65             self.create(rec['name'], rec['vref'])
66             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
67
68         self.keys = ''
69         self.rspec = {}
70         self.initscript = ''
71         self.slice_id = rec['slice_id']
72         self.disk_usage_initialized = False
73         self.initscriptchanged = False
74         self.enabled = True
75         self.configure(rec)
76
77     @staticmethod
78     def create(name, vref = None):
79         logger.verbose('Sliver_VS:create - name=%s'%name)
80         if vref is None:
81             logger.log("creating %s : no vref attached, this is unexpected"%name)
82             vref='default'
83         # used to look in /etc/planetlab/family, now relies on the 'family' extra attribute in GetSlivers()
84         # which for legacy is still exposed here as the 'vref' key
85         
86         # check the template exists -- there's probably a better way..
87         if not os.path.isdir ("/vservers/.vref/%s"%vref):
88             # find a resonable default
89             if os.path.isfile ("/etc/planetlab/slicefamily"):
90                 default=file("/etc/planetlab/slicefamily").read().strip()
91             else:
92                 default='default'
93                 logger.log("creating %s : /etc/planetlab/slicefamily not found, this is unexpected"%name)
94             logger.log("creating %s - vref %s not found, using default %s"%(name,vref,default))
95             vref=default
96
97         # guess arch
98         try:
99             (x,y,arch)=vref.split('-')
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)
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))
116             logger.log('%s: set personality to %s'%(name,personality(arch)))
117
118     @staticmethod
119     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
120
121     def configure(self, rec):
122         new_rspec = rec['_rspec']
123         if new_rspec != self.rspec:
124             self.rspec = new_rspec
125             self.set_resources()
126
127         new_initscript = rec['initscript']
128         if new_initscript != self.initscript:
129             self.initscript = new_initscript
130             self.initscriptchanged = True
131
132         accounts.Account.configure(self, rec)  # install ssh keys
133
134     def start(self, delay=0):
135         if self.rspec['enabled'] > 0:
136             logger.log('%s: starting in %d seconds' % (self.name, delay))
137             time.sleep(delay)
138             # VServer.start calls fork() internally, 
139             # so just close the nonstandard fds and fork once to avoid creating zombies
140             child_pid = os.fork()
141             if child_pid == 0:
142                 if self.initscriptchanged:
143                     logger.log('%s: installing initscript' % self.name)
144                     def install_initscript():
145                         flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
146                         fd = os.open('/etc/rc.vinit', flags, 0755)
147                         os.write(fd, self.initscript)
148                         os.close(fd)
149                     try:
150                         self.chroot_call(install_initscript)
151                     except: logger.log_exc(self.name)
152                 tools.close_nonstandard_fds()
153                 vserver.VServer.start(self)
154                 os._exit(0)
155             else: 
156                 os.waitpid(child_pid, 0)
157                 self.initscriptchanged = False
158         else: logger.log('%s: not starting, is not enabled' % self.name)
159
160     def stop(self):
161         logger.log('%s: stopping' % self.name)
162         vserver.VServer.stop(self)
163
164     def is_running(self): 
165         return vserver.VServer.is_running(self)
166
167     def set_resources(self,setup=False):
168         disk_max = self.rspec['disk_max']
169         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
170         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
171             if not self.disk_usage_initialized:
172                 self.vm_running = False
173                 Sliver_VS._init_disk_info_sem.acquire()
174                 logger.log('%s: computing disk usage: beginning' % self.name)
175                 try: self.init_disk_info()
176                 finally: Sliver_VS._init_disk_info_sem.release()
177                 logger.log('%s: computing disk usage: ended' % self.name)
178                 self.disk_usage_initialized = True
179             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
180         except:
181             logger.log('%s: failed to set max disk usage' % self.name)
182             logger.log_exc(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('%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('%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("%s: opening %s"%(self.name,path))
211                         flags = os.O_WRONLY
212                         fd = os.open(path, flags)
213                         logger.log("%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("%s: could not set %s=%s"%(self.name,key,self.rspec[key]))
218                         logger.log("%s: error = %s"%(self.name,e))
219
220
221         if self.rspec['enabled'] > 0:
222             if cpu_pct > 0:
223                 logger.log('%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('%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('%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             if self.is_running():
240                 logger.log("%s: Setting name to %s" % (self.name, self.slice_id),2) 
241                 self.setname(self.slice_id) 
242                 ### Sapan's change needs more work - /etc/vservers not available here, we're in the chroot
243                 #logger.log("%s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id),2)
244                 #file('/etc/vservers/%s/slice_id' % self.name, 'w').write(self.slice_id)
245
246             if self.enabled == False:
247                 self.enabled = True
248                 self.start()
249  
250             if False: # Does not work properly yet.
251                 if self.have_limits_changed():
252                     logger.log('%s: limits have changed --- restarting' % self.name)
253                     stopcount = 10
254                     while self.is_running() and stopcount > 0:
255                         self.stop()
256                         delay = 1
257                         time.sleep(delay)
258                         stopcount = stopcount - 1
259                     self.start()
260
261         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
262             logger.log('%s: disabling remote login' % self.name)
263             self.set_sched_config(0, 0)
264             self.enabled = False
265             self.stop()