set personality for slivers with a different arch
[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 commands
23
24 import vserver
25
26 import accounts
27 import logger
28 import tools
29 from threading import BoundedSemaphore
30
31 globalsem = BoundedSemaphore()
32
33 # special constant that tells vserver to keep its existing settings
34 KEEP_LIMIT = vserver.VC_LIM_KEEP
35
36 # populate the sliver/vserver specific default allocations table,
37 # which is used to look for slice attributes
38 DEFAULT_ALLOCATION = {}
39 for rlimit in vserver.RLIMITS.keys():
40     rlim = rlimit.lower()
41     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
42     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
43     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
44
45 class Sliver_VS(accounts.Account, vserver.VServer):
46     """This class wraps vserver.VServer to make its interface closer to what we need."""
47
48     SHELL = '/bin/vsh'
49     TYPE = 'sliver.VServer'
50     _init_disk_info_sem = globalsem
51
52     def __init__(self, rec):
53         logger.verbose ('initing Sliver_VS with name=%s'%rec['name'])
54         try:
55             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
56         except Exception, err:
57             if not isinstance(err, vserver.NoSuchVServer):
58                 # Probably a bad vserver or vserver configuration file
59                 logger.log_exc(self.name)
60                 logger.log('%s: recreating bad vserver' % rec['name'])
61                 self.destroy(rec['name'])
62             self.create(rec['name'], rec['vref'])
63             vserver.VServer.__init__(self, rec['name'],logfile='/var/log/nm')
64
65         self.keys = ''
66         self.rspec = {}
67         self.initscript = ''
68         self.slice_id = rec['slice_id']
69         self.disk_usage_initialized = False
70         self.initscriptchanged = False
71         self.configure(rec)
72
73     _root_context_arch=None
74     @staticmethod 
75     def root_context_arch():
76         if not Sliver_VS._root_context_arch:
77             Sliver_VS._root_context_arch=commands.getoutput("uname -i")
78         return Sliver_VS._root_context_arch
79
80     @staticmethod
81     def personality (arch):
82         personality="linux32"
83         if arch.find("64")>=0:
84             personality="linux64"
85         return personality
86
87     @staticmethod
88     def create(name, vref = None):
89         logger.verbose('Sliver_VS:create - name=%s'%name)
90         if vref is None:
91             vref='default'
92         try:
93             ### locating the right slicefamily
94             # this is a first draft, and more a proof of concept thing
95             # the idea is to parse vref for dash-separated wishes,
96             # and to project these against the defaults
97             # so e.g. if the default slice family (as found in /etc/planetlab/slicefamily)
98             # is planetlab-f8-i386, then here is what we get
99             # vref=x86_64             -> vuseradd -t planetlab-f8-x86_64 
100             # vref=centos5            -> vuseradd -t planetlab-centos5-i386 
101             # vref=centos5-onelab     -> vuseradd -t onelab-centos5-i386 
102             # vref=planetflow         -> vuseradd -t planetflow-f8-i386
103             # vref=x86_64-planetflow  -> vuseradd -t planetflow-f8-x86_64
104
105             # default
106             default=file("/etc/planetlab/slicefamily").read().strip()
107             (pldistro,fcdistro,arch) = default.split("-")
108
109             known_archs = [ 'i386', 'x86_64' ]
110             known_fcdistros = [ 'f8', 'f9', 'centos5' ]
111             # from the slice attribute: cut dashes and try to figure the meaning
112             slice_wishes = vref.split("-")
113             for wish in slice_wishes:
114                 if wish in known_archs:
115                     arch=wish
116                 elif wish in known_fcdistros:
117                     fcdistro=wish
118                 else:
119                     pldistro=wish
120
121             # rejoin the parts
122             refname="-".join( (pldistro,fcdistro,arch) )
123
124             # check the template exists -- there's probably a better way..
125             if not os.path.isdir ("/vservers/.vref/%s"%refname):
126                 logger.verbose("%s (%s) : vref %s not found, using default %s"%(
127                         name,vref,refname,default))
128                 refname=default
129                 # reset so arch is right
130                 (pldistro,fcdistro,arch) = default.split("-")
131                 # could check again, but as we have /etc/slicefamily 
132                 # there's probably no /vservers/.vref/default
133
134         except IOError:
135             # have not found slicefamily
136             logger.verbose("%s (%s): legacy node - using fallback vrefname 'default'"%(name,vref))
137             # for legacy nodes
138             refname="default"
139             arch="i386"
140         except:
141             import traceback
142             logger.log("%s (%s) : unexpected error follows - using 'default'"%(name,vref))
143             logger.log(traceback.format_exc())
144             refname="default"
145             arch="i386"
146             
147         logger.log_call('/usr/sbin/vuseradd', '-t', refname, name)
148         # export slicename to the slice in /etc/slicename
149         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
150         # set personality: only if needed (if arch's differ)
151         if Sliver_VS.root_context_arch() != arch:
152             file('/etc/vservers/%s/personality' % name, 'w').write(Sliver_VS.personality(arch))
153             logger.log('%s: set personality to %s'%(name,Sliver_VS.personality(arch)))
154
155     @staticmethod
156     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
157
158     def configure(self, rec):
159         new_rspec = rec['_rspec']
160         if new_rspec != self.rspec:
161             self.rspec = new_rspec
162             self.set_resources()
163
164         new_initscript = rec['initscript']
165         if new_initscript != self.initscript:
166             self.initscript = new_initscript
167             logger.log('%s: installing initscript' % self.name)
168             def install_initscript():
169                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
170                 fd = os.open('/etc/rc.vinit', flags, 0755)
171                 os.write(fd, new_initscript)
172                 os.close(fd)
173             try:
174                 self.chroot_call(install_initscript)
175                 self.initscriptchanged = True
176             except: logger.log_exc(self.name)
177
178         accounts.Account.configure(self, rec)  # install ssh keys
179
180     def start(self, delay=0):
181         if self.rspec['enabled'] > 0:
182             logger.log('%s: starting in %d seconds' % (self.name, delay))
183             time.sleep(delay)
184             child_pid = os.fork()
185             if child_pid == 0:
186                 # VServer.start calls fork() internally, 
187                 # so just close the nonstandard fds and fork once to avoid creating zombies
188                 tools.close_nonstandard_fds()
189                 vserver.VServer.start(self)
190                 os._exit(0)
191             else: os.waitpid(child_pid, 0)
192         else: logger.log('%s: not starting, is not enabled' % self.name)
193         self.initscriptchanged = False
194
195     def stop(self):
196         logger.log('%s: stopping' % self.name)
197         vserver.VServer.stop(self)
198
199     def set_resources(self):
200         disk_max = self.rspec['disk_max']
201         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
202         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
203             if not self.disk_usage_initialized:
204                 self.vm_running = False
205                 logger.log('%s: computing disk usage: beginning' % self.name)
206                 Sliver_VS._init_disk_info_sem.acquire()
207                 try: self.init_disk_info()
208                 finally: Sliver_VS._init_disk_info_sem.release()
209                 logger.log('%s: computing disk usage: ended' % self.name)
210                 self.disk_usage_initialized = True
211             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
212         except:
213             logger.log('%s: failed to set max disk usage' % self.name)
214             logger.log_exc(self.name)
215
216         # get/set the min/soft/hard values for all of the vserver
217         # related RLIMITS.  Note that vserver currently only
218         # implements support for hard limits.
219         for limit in vserver.RLIMITS.keys():
220             type = limit.lower()
221             minimum  = self.rspec['%s_min'%type]
222             soft = self.rspec['%s_soft'%type]
223             hard = self.rspec['%s_hard'%type]
224             update = self.set_rlimit(limit, hard, soft, minimum)
225             if update:
226                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
227                            % (self.name, type, hard, soft, minimum))
228
229         self.set_capabilities_config(self.rspec['capabilities'])
230         if self.rspec['capabilities']:
231             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
232
233         cpu_pct = self.rspec['cpu_pct']
234         cpu_share = self.rspec['cpu_share']
235
236         if self.rspec['enabled'] > 0:
237             if cpu_pct > 0:
238                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
239             else:
240                 cpu_pct = 0
241
242             if cpu_share > 0:
243                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
244             else:
245                 cpu_share = 0
246
247             self.set_sched_config(cpu_pct, cpu_share)
248             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
249             if self.rspec['ip_addresses'] != '0.0.0.0':
250                 logger.log('%s: setting IP address(es) to %s' % \
251                 (self.name, self.rspec['ip_addresses']))
252             self.set_ipaddresses_config(self.rspec['ip_addresses'])
253
254             if self.is_running():
255                 logger.log("%s: Setting name to %s" % (self.name, self.slice_id),2)
256                 self.setname(self.slice_id)
257  
258             if False: # Does not work properly yet.
259                 if self.have_limits_changed():
260                     logger.log('%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('%s: disabling remote login' % self.name)
271             self.set_sched_config(0, 0)
272             self.stop()