fixed and moved the server-side yum scripts, from nodeconfig/yum to BootstrapFS/nodec...
[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, 
84         # now relies on the 'GetSliceFamily' extra attribute in GetSlivers()
85         # which for legacy is still exposed here as the 'vref' key
86         
87         # check the template exists -- there's probably a better way..
88         if not os.path.isdir ("/vservers/.vref/%s"%vref):
89             logger.log ("%s: ERROR Could not create sliver - vreference image %s not found"%(name,vref))
90             return
91
92         # guess arch
93         try:
94             (x,y,arch)=vref.split('-')
95         except:
96             arch='i386'
97             
98         def personality (arch):
99             personality="linux32"
100             if arch.find("64")>=0:
101                 personality="linux64"
102             return personality
103
104         logger.log_call('/usr/sbin/vuseradd', '-t', vref, name)
105         # export slicename to the slice in /etc/slicename
106         file('/vservers/%s/etc/slicename' % name, 'w').write(name)
107         file('/vservers/%s/etc/slicefamily' % name, 'w').write(vref)
108         # set personality: only if needed (if arch's differ)
109         if tools.root_context_arch() != arch:
110             file('/etc/vservers/%s/personality' % name, 'w').write(personality(arch))
111             logger.log('%s: set personality to %s'%(name,personality(arch)))
112
113     @staticmethod
114     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
115
116     def configure(self, rec):
117         new_rspec = rec['_rspec']
118         if new_rspec != self.rspec:
119             self.rspec = new_rspec
120             self.set_resources()
121
122         new_initscript = rec['initscript']
123         if new_initscript != self.initscript:
124             self.initscript = new_initscript
125             self.initscriptchanged = True
126
127         accounts.Account.configure(self, rec)  # install ssh keys
128
129     def start(self, delay=0):
130         if self.rspec['enabled'] > 0:
131             logger.log('%s: starting in %d seconds' % (self.name, delay))
132             time.sleep(delay)
133             # VServer.start calls fork() internally, 
134             # so just close the nonstandard fds and fork once to avoid creating zombies
135             child_pid = os.fork()
136             if child_pid == 0:
137                 if self.initscriptchanged:
138                     logger.log('%s: installing initscript' % self.name)
139                     def install_initscript():
140                         flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
141                         fd = os.open('/etc/rc.vinit', flags, 0755)
142                         os.write(fd, self.initscript)
143                         os.close(fd)
144                     try:
145                         self.chroot_call(install_initscript)
146                     except: logger.log_exc(self.name)
147                 tools.close_nonstandard_fds()
148                 vserver.VServer.start(self)
149                 os._exit(0)
150             else: 
151                 os.waitpid(child_pid, 0)
152                 self.initscriptchanged = False
153         else: logger.log('%s: not starting, is not enabled' % self.name)
154
155     def stop(self):
156         logger.log('%s: stopping' % self.name)
157         vserver.VServer.stop(self)
158
159     def is_running(self): 
160         return vserver.VServer.is_running(self)
161
162     def set_resources(self,setup=False):
163         disk_max = self.rspec['disk_max']
164         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
165         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
166             if not self.disk_usage_initialized:
167                 self.vm_running = False
168                 Sliver_VS._init_disk_info_sem.acquire()
169                 logger.log('%s: computing disk usage: beginning' % self.name)
170                 try: self.init_disk_info()
171                 finally: Sliver_VS._init_disk_info_sem.release()
172                 logger.log('%s: computing disk usage: ended' % self.name)
173                 self.disk_usage_initialized = True
174             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
175         except:
176             logger.log('%s: failed to set max disk usage' % self.name)
177             logger.log_exc(self.name)
178
179         # get/set the min/soft/hard values for all of the vserver
180         # related RLIMITS.  Note that vserver currently only
181         # implements support for hard limits.
182         for limit in vserver.RLIMITS.keys():
183             type = limit.lower()
184             minimum  = self.rspec['%s_min'%type]
185             soft = self.rspec['%s_soft'%type]
186             hard = self.rspec['%s_hard'%type]
187             update = self.set_rlimit(limit, hard, soft, minimum)
188             if update:
189                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
190                            % (self.name, type, hard, soft, minimum))
191
192         self.set_capabilities_config(self.rspec['capabilities'])
193         if self.rspec['capabilities']:
194             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
195
196         cpu_pct = self.rspec['cpu_pct']
197         cpu_share = self.rspec['cpu_share']
198
199         if setup:
200             for key in self.rspec.keys():
201                 if key.find('sysctl.') == 0:
202                     sysctl=key.split('.')
203                     try:
204                         path="/proc/sys/%s" % ("/".join(sysctl[1:]))
205                         logger.log("%s: opening %s"%(self.name,path))
206                         flags = os.O_WRONLY
207                         fd = os.open(path, flags)
208                         logger.log("%s: writing %s=%s"%(self.name,key,self.rspec[key]))
209                         os.write(fd,self.rspec[key])
210                         os.close(fd)
211                     except IOError, e:
212                         logger.log("%s: could not set %s=%s"%(self.name,key,self.rspec[key]))
213                         logger.log("%s: error = %s"%(self.name,e))
214
215
216         if self.rspec['enabled'] > 0:
217             if cpu_pct > 0:
218                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
219             else:
220                 cpu_pct = 0
221
222             if cpu_share > 0:
223                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
224             else:
225                 cpu_share = 0
226
227             self.set_sched_config(cpu_pct, cpu_share)
228             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
229             if self.rspec['ip_addresses'] != '0.0.0.0':
230                 logger.log('%s: setting IP address(es) to %s' % \
231                 (self.name, self.rspec['ip_addresses']))
232             self.set_ipaddresses_config(self.rspec['ip_addresses'])
233
234             if self.is_running():
235                 logger.log("%s: Setting name to %s" % (self.name, self.slice_id)) 
236                 self.setname(self.slice_id) 
237                 ### Sapan's change needs more work 
238                 # raise IOException, file does not get created
239                 # might be that /etc/vservers is not available here, are we in the chroot ?
240                 #logger.log("%s: Storing slice id of %s for PlanetFlow" % (self.name, self.slice_id))
241                 #file('/etc/vservers/%s/slice_id' % self.name, 'w').write(self.slice_id)
242
243             if self.enabled == False:
244                 self.enabled = True
245                 self.start()
246  
247             if False: # Does not work properly yet.
248                 if self.have_limits_changed():
249                     logger.log('%s: limits have changed --- restarting' % self.name)
250                     stopcount = 10
251                     while self.is_running() and stopcount > 0:
252                         self.stop()
253                         delay = 1
254                         time.sleep(delay)
255                         stopcount = stopcount - 1
256                     self.start()
257
258         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
259             logger.log('%s: disabling remote login' % self.name)
260             self.set_sched_config(0, 0)
261             self.enabled = False
262             self.stop()