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