setname as part of set_resources. Better than stupid sleep while part of start(...
[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
160         else: logger.log('%s: not starting, is not enabled' % self.name)
161         self.initscriptchanged = False
162
163     def stop(self):
164         logger.log('%s: stopping' % self.name)
165         vserver.VServer.stop(self)
166
167     def set_resources(self):
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                 logger.log('%s: computing disk usage: beginning' % self.name)
174                 Sliver_VS._init_disk_info_sem.acquire()
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 self.rspec['enabled'] > 0:
205             if cpu_pct > 0:
206                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
207             else:
208                 cpu_pct = 0
209
210             if cpu_share > 0:
211                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
212             else:
213                 cpu_share = 0
214
215             self.set_sched_config(cpu_pct, cpu_share)
216             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
217             if self.rspec['ip_addresses'] != '0.0.0.0':
218                 logger.log('%s: setting IP address(es) to %s' % \
219                 (self.name, self.rspec['ip_addresses']))
220             self.set_ipaddresses_config(self.rspec['ip_addresses'])
221
222             if self.is_running():
223                 logger.log("%s: Setting name to %s" % (self.name, self.slice_id),2)
224                 self.setname(self.slice_id)
225  
226             if False: # Does not work properly yet.
227                 if self.have_limits_changed():
228                     logger.log('%s: limits have changed --- restarting' % self.name)
229                     stopcount = 10
230                     while self.is_running() and stopcount > 0:
231                         self.stop()
232                         delay = 1
233                         time.sleep(delay)
234                         stopcount = stopcount - 1
235                     self.start()
236
237         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
238             logger.log('%s: disabling remote login' % self.name)
239             self.set_sched_config(0, 0)
240             self.stop()