this is what i really wanted to do. temporary fix. this needs to be cleaned up
[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
93             known_archs = [ 'i386', 'x86_64' ]
94             known_fcdistros = [ 'f8', 'f9', 'centos5' ]
95             # from the slice attribute: cut dashes and try to figure the meaning
96             slice_wishes = vref.split("-")
97             for wish in slice_wishes:
98                 if wish in known_archs:
99                     arch=wish
100                 elif wish in known_fcdistros:
101                     fcdistro=wish
102                 else:
103                     pldistro=wish
104
105             # rejoin the parts
106             refname="-".join( (pldistro,fcdistro,arch) )
107
108             # check the templates exists -- there's probably a better way..
109             if os.path.isdir ("/vservers/.vref/%s"% vref): refname = vref
110
111             if not os.path.isdir ("/vservers/.vref/%s"% refname):
112                 logger.verbose("%s (%s) : vref %s not found, using default %s"%(
113                         name,vref,refname,default))
114                 refname=default
115                 # could check again, but as we have /etc/slicefamily 
116                 # there's probably no /vservers/.vref/default
117
118         except IOError:
119             # have not found slicefamily
120             logger.verbose("%s (%s): legacy node - using fallback vrefname 'default'"%(name,vref))
121                 # for legacy nodes
122             refname="default"
123         except:
124             import traceback
125             logger.log("%s (%s) : unexpected error follows - using 'default'"%(
126                     name,vref))
127             logger.log(traceback.format_exc())
128             refname="default"
129             
130         logger.log_call('/usr/sbin/vuseradd', '-t', refname, name)
131         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
132
133     @staticmethod
134     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
135
136     def configure(self, rec):
137         new_rspec = rec['_rspec']
138         if new_rspec != self.rspec:
139             self.rspec = new_rspec
140             self.set_resources()
141
142         new_initscript = rec['initscript']
143         if new_initscript != self.initscript:
144             self.initscript = new_initscript
145             logger.log('%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, new_initscript)
150                 os.close(fd)
151             try:
152                 self.chroot_call(install_initscript)
153                 self.initscriptchanged = True
154             except: logger.log_exc(self.name)
155
156         accounts.Account.configure(self, rec)  # install ssh keys
157
158     def start(self, delay=0):
159         if self.rspec['enabled'] > 0:
160             logger.log('%s: starting in %d seconds' % (self.name, delay))
161             time.sleep(delay)
162             child_pid = os.fork()
163             if child_pid == 0:
164                 # VServer.start calls fork() internally, 
165                 # so just close the nonstandard fds and fork once to avoid creating zombies
166                 tools.close_nonstandard_fds()
167                 vserver.VServer.start(self)
168                 os._exit(0)
169             else: os.waitpid(child_pid, 0)
170         else: logger.log('%s: not starting, is not enabled' % self.name)
171         self.initscriptchanged = False
172
173     def stop(self):
174         logger.log('%s: stopping' % self.name)
175         vserver.VServer.stop(self)
176
177     def set_resources(self):
178         disk_max = self.rspec['disk_max']
179         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
180         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
181             if not self.disk_usage_initialized:
182                 self.vm_running = False
183                 logger.log('%s: computing disk usage: beginning' % self.name)
184                 Sliver_VS._init_disk_info_sem.acquire()
185                 try: self.init_disk_info()
186                 finally: Sliver_VS._init_disk_info_sem.release()
187                 logger.log('%s: computing disk usage: ended' % self.name)
188                 self.disk_usage_initialized = True
189             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
190         except:
191             logger.log('%s: failed to set max disk usage' % self.name)
192             logger.log_exc(self.name)
193
194         # get/set the min/soft/hard values for all of the vserver
195         # related RLIMITS.  Note that vserver currently only
196         # implements support for hard limits.
197         for limit in vserver.RLIMITS.keys():
198             type = limit.lower()
199             minimum  = self.rspec['%s_min'%type]
200             soft = self.rspec['%s_soft'%type]
201             hard = self.rspec['%s_hard'%type]
202             update = self.set_rlimit(limit, hard, soft, minimum)
203             if update:
204                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
205                            % (self.name, type, hard, soft, minimum))
206
207         self.set_capabilities_config(self.rspec['capabilities'])
208         if self.rspec['capabilities']:
209             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
210
211         cpu_pct = self.rspec['cpu_pct']
212         cpu_share = self.rspec['cpu_share']
213
214         if self.rspec['enabled'] > 0:
215             if cpu_pct > 0:
216                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
217             else:
218                 cpu_pct = 0
219
220             if cpu_share > 0:
221                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
222             else:
223                 cpu_share = 0
224
225             self.set_sched_config(cpu_pct, cpu_share)
226             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
227             if self.rspec['ip_addresses'] != '0.0.0.0':
228                 logger.log('%s: setting IP address(es) to %s' % \
229                 (self.name, self.rspec['ip_addresses']))
230             self.set_ipaddresses_config(self.rspec['ip_addresses'])
231
232             if self.is_running():
233                 logger.log("%s: Setting name to %s" % (self.name, self.slice_id),2)
234                 self.setname(self.slice_id)
235  
236             if False: # Does not work properly yet.
237                 if self.have_limits_changed():
238                     logger.log('%s: limits have changed --- restarting' % self.name)
239                     stopcount = 10
240                     while self.is_running() and stopcount > 0:
241                         self.stop()
242                         delay = 1
243                         time.sleep(delay)
244                         stopcount = stopcount - 1
245                     self.start()
246
247         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
248             logger.log('%s: disabling remote login' % self.name)
249             self.set_sched_config(0, 0)
250             self.stop()