Setname failed because context wasn't setup. Added busy wait in VServer.is_running...
[nodemanager.git] / sliver_vs.py
1 """VServer slivers.
2
3 There are a couple of tricky things going on here.  First, the kernel
4 needs disk usage information in order to enforce the quota.  However,
5 determining disk usage redundantly strains the disks.  Thus, the
6 Sliver_VS.disk_usage_initialized flag is used to determine whether
7 this initialization has been made.
8
9 Second, it's not currently possible to set the scheduler parameters
10 for a sliver unless that sliver has a running process.  /bin/vsh helps
11 us out by reading the configuration file so that it can set the
12 appropriate limits after entering the sliver context.  Making the
13 syscall that actually sets the parameters gives a harmless error if no
14 process is running.  Thus we keep vm_running on when setting scheduler
15 parameters so that set_sched_params() always makes the syscall, and we
16 don't have to guess if there is a running process or not.
17 """
18
19 import errno
20 import os, os.path
21 import time
22 import vserver
23
24 import accounts
25 import logger
26 import tools
27 from threading import BoundedSemaphore
28
29 globalsem = BoundedSemaphore()
30
31 # special constant that tells vserver to keep its existing settings
32 KEEP_LIMIT = vserver.VC_LIM_KEEP
33
34 # populate the sliver/vserver specific default allocations table,
35 # which is used to look for slice attributes
36 DEFAULT_ALLOCATION = {}
37 for rlimit in vserver.RLIMITS.keys():
38     rlim = rlimit.lower()
39     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
40     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
41     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
42
43 class Sliver_VS(accounts.Account, vserver.VServer):
44     """This class wraps vserver.VServer to make its interface closer to what we need."""
45
46     SHELL = '/bin/vsh'
47     TYPE = 'sliver.VServer'
48     _init_disk_info_sem = globalsem
49
50     def __init__(self, rec):
51         logger.verbose ('initing Sliver_VS with name=%s'%rec['name'])
52         try:
53             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
54         except Exception, err:
55             if not isinstance(err, vserver.NoSuchVServer):
56                 # Probably a bad vserver or vserver configuration file
57                 logger.log_exc(self.name)
58                 logger.log('%s: recreating bad vserver' % rec['name'])
59                 self.destroy(rec['name'])
60             self.create(rec['name'], rec['vref'])
61             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
62
63         self.keys = ''
64         self.rspec = {}
65         self.initscript = ''
66         self.slice_id = rec['slice_id']
67         self.disk_usage_initialized = False
68         self.initscriptchanged = False
69         self.configure(rec)
70
71     @staticmethod
72     def create(name, vref = None):
73         logger.verbose('Sliver_VS:create - name=%s'%name)
74         if vref is None:
75             vref='default'
76         try:
77             ### locating the right slicefamily
78             # this is a first draft, and more a proof of concept thing
79             # the idea is to parse vref for dash-separated wishes,
80             # and to project these against the defaults
81             # so e.g. if the default slice family (as found in /etc/planetlab/slicefamily)
82             # is planetlab-f8-i386, then here is what we get
83             # vref=x86_64             -> vuseradd -t planetlab-f8-x86_64 
84             # vref=centos5            -> vuseradd -t planetlab-centos5-i386 
85             # vref=centos5-onelab     -> vuseradd -t onelab-centos5-i386 
86             # vref=planetflow         -> vuseradd -t planetflow-f8-i386
87             # vref=x86_64-planetflow  -> vuseradd -t planetflow-f8-x86_64
88
89             # default
90             default=file("/etc/planetlab/slicefamily").read().strip()
91             (pldistro,fcdistro,arch) = default.split("-")
92             # from the slice attribute: cut dashes and try to figure the meaning
93             slice_wishes = vref.split("-")
94             for wish in slice_wishes:
95                 if wish == "i386" or wish == "x86_64":
96                     arch=wish
97                 elif wish == "f8" or wish == "centos5" :
98                     fcdistro=wish
99                 else:
100                     pldistro=wish
101
102             # rejoin the parts
103             refname="-".join( (pldistro,fcdistro,arch) )
104
105             # check the templates exists -- there's probably a better way..
106             if not os.path.isdir ("/vservers/.vref/%s"%refname):
107                 logger.verbose("%s (%s) : vref %s not found, using default %s"%(
108                         name,vref,refname,default))
109                 refname=default
110                 # could check again, but as we have /etc/slicefamily 
111                 # there's probably no /vservers/.vref/default
112
113         except IOError:
114             # have not found slicefamily
115             logger.verbose("%s (%s): legacy node - using fallback vrefname 'default'"%(name,vref))
116                 # for legacy nodes
117             refname="default"
118         except:
119             import traceback
120             logger.log("%s (%s) : unexpected error follows - using 'default'"%(
121                     name,vref))
122             logger.log(traceback.format_exc())
123             refname="default"
124             
125         logger.log_call('/usr/sbin/vuseradd', '-t', refname, name)
126         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
127
128     @staticmethod
129     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
130
131     def configure(self, rec):
132         new_rspec = rec['_rspec']
133         if new_rspec != self.rspec:
134             self.rspec = new_rspec
135             self.set_resources()
136
137         new_initscript = rec['initscript']
138         if new_initscript != self.initscript:
139             self.initscript = new_initscript
140             logger.log('%s: installing initscript' % self.name)
141             def install_initscript():
142                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
143                 fd = os.open('/etc/rc.vinit', flags, 0755)
144                 os.write(fd, new_initscript)
145                 os.close(fd)
146             try:
147                 self.chroot_call(install_initscript)
148                 self.initscriptchanged = True
149             except: logger.log_exc(self.name)
150
151         accounts.Account.configure(self, rec)  # install ssh keys
152
153     def start(self, delay=0):
154         if self.rspec['enabled'] > 0:
155             logger.log('%s: starting in %d seconds' % (self.name, delay))
156             time.sleep(delay)
157             # VServer.start calls fork() internally
158             vserver.VServer.start(self)
159             # Watch for 5 mins to see if slice is running before setting the name
160             # It would make sense to do this as part of start in VServer, but the name
161             # comes from NM.  Also, the name would only change in NM.  Name can only be
162             # set from root context, so overloading chcontext wont work;  chcontext, setname
163             # will fail, and in the converse the context isn't setup in the kernel.
164             for i in range(0,60):
165                 time.sleep(5)
166                 if vserver.VServer.is_running(self):
167                     # Set the vciVHI_CONTEXT to slice_id for 
168                     # fprobe-ulog to mark packets with.
169                     logger.log("%s: Setting name to %s" % (self.name, self.slice_id),2)
170                     self.setname(self.slice_id)
171                     break
172
173         else: logger.log('%s: not starting, is not enabled' % self.name)
174         self.initscriptchanged = False
175
176     def stop(self):
177         logger.log('%s: stopping' % self.name)
178         vserver.VServer.stop(self)
179
180     def set_resources(self):
181         disk_max = self.rspec['disk_max']
182         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
183         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
184             if not self.disk_usage_initialized:
185                 self.vm_running = False
186                 logger.log('%s: computing disk usage: beginning' % self.name)
187                 Sliver_VS._init_disk_info_sem.acquire()
188                 try: self.init_disk_info()
189                 finally: Sliver_VS._init_disk_info_sem.release()
190                 logger.log('%s: computing disk usage: ended' % self.name)
191                 self.disk_usage_initialized = True
192             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
193         except:
194             logger.log('%s: failed to set max disk usage' % self.name)
195             logger.log_exc(self.name)
196
197         # get/set the min/soft/hard values for all of the vserver
198         # related RLIMITS.  Note that vserver currently only
199         # implements support for hard limits.
200         for limit in vserver.RLIMITS.keys():
201             type = limit.lower()
202             minimum  = self.rspec['%s_min'%type]
203             soft = self.rspec['%s_soft'%type]
204             hard = self.rspec['%s_hard'%type]
205             update = self.set_rlimit(limit, hard, soft, minimum)
206             if update:
207                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
208                            % (self.name, type, hard, soft, minimum))
209
210         self.set_capabilities_config(self.rspec['capabilities'])
211         if self.rspec['capabilities']:
212             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
213
214         cpu_pct = self.rspec['cpu_pct']
215         cpu_share = self.rspec['cpu_share']
216
217         if self.rspec['enabled'] > 0:
218             if cpu_pct > 0:
219                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
220             else:
221                 cpu_pct = 0
222
223             if cpu_share > 0:
224                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
225             else:
226                 cpu_share = 0
227
228             self.set_sched_config(cpu_pct, cpu_share)
229             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
230             if self.rspec['ip_addresses'] != '0.0.0.0':
231                 logger.log('%s: setting IP address(es) to %s' % \
232                 (self.name, self.rspec['ip_addresses']))
233             self.set_ipaddresses_config(self.rspec['ip_addresses'])
234
235             if False: # Does not work properly yet.
236                 if self.have_limits_changed():
237                     logger.log('%s: limits have changed --- restarting' % self.name)
238                     stopcount = 10
239                     while self.is_running() and stopcount > 0:
240                         self.stop()
241                         delay = 1
242                         time.sleep(delay)
243                         stopcount = stopcount - 1
244                     self.start()
245
246         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
247             logger.log('%s: disabling remote login' % self.name)
248             self.set_sched_config(0, 0)
249             self.stop()