Set slice_id as vcVHI_CONTEXT.
[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.disk_usage_initialized = False
67         self.initscriptchanged = False
68         self.configure(rec)
69
70     @staticmethod
71     def create(name, vref = None):
72         logger.verbose('Sliver_VS:create - name=%s'%name)
73         if vref is None:
74             vref='default'
75         try:
76             ### locating the right slicefamily
77             # this is a first draft, and more a proof of concept thing
78             # the idea is to parse vref for dash-separated wishes,
79             # and to project these against the defaults
80             # so e.g. if the default slice family (as found in /etc/planetlab/slicefamily)
81             # is planetlab-f8-i386, then here is what we get
82             # vref=x86_64             -> vuseradd -t planetlab-f8-x86_64 
83             # vref=centos5            -> vuseradd -t planetlab-centos5-i386 
84             # vref=centos5-onelab     -> vuseradd -t onelab-centos5-i386 
85             # vref=planetflow         -> vuseradd -t planetflow-f8-i386
86             # vref=x86_64-planetflow  -> vuseradd -t planetflow-f8-x86_64
87
88             # default
89             default=file("/etc/planetlab/slicefamily").read().strip()
90             (pldistro,fcdistro,arch) = default.split("-")
91             # from the slice attribute: cut dashes and try to figure the meaning
92             slice_wishes = vref.split("-")
93             for wish in slice_wishes:
94                 if wish == "i386" or wish == "x86_64":
95                     arch=wish
96                 elif wish == "f8" or wish == "centos5" :
97                     fcdistro=wish
98                 else:
99                     pldistro=wish
100
101             # rejoin the parts
102             refname="-".join( (pldistro,fcdistro,arch) )
103
104             # check the templates exists -- there's probably a better way..
105             if not os.path.isdir ("/vservers/.vref/%s"%refname):
106                 logger.verbose("%s (%s) : vref %s not found, using default %s"%(
107                         name,vref,refname,default))
108                 refname=default
109                 # could check again, but as we have /etc/slicefamily 
110                 # there's probably no /vservers/.vref/default
111
112         except IOError:
113             # have not found slicefamily
114             logger.verbose("%s (%s): legacy node - using fallback vrefname 'default'"%(name,vref))
115                 # for legacy nodes
116             refname="default"
117         except:
118             import traceback
119             logger.log("%s (%s) : unexpected error follows - using 'default'"%(
120                     name,vref))
121             logger.log(traceback.format_exc())
122             refname="default"
123             
124         logger.log_call('/usr/sbin/vuseradd', '-t', refname, name)
125         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
126
127     @staticmethod
128     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
129
130     def configure(self, rec):
131         new_rspec = rec['_rspec']
132         if new_rspec != self.rspec:
133             self.rspec = new_rspec
134             self.set_resources()
135
136         new_initscript = rec['initscript']
137         if new_initscript != self.initscript:
138             self.initscript = new_initscript
139             logger.log('%s: installing initscript' % self.name)
140             def install_initscript():
141                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
142                 fd = os.open('/etc/rc.vinit', flags, 0755)
143                 os.write(fd, new_initscript)
144                 os.close(fd)
145             try:
146                 self.chroot_call(install_initscript)
147                 self.initscriptchanged = True
148             except: logger.log_exc(self.name)
149
150         accounts.Account.configure(self, rec)  # install ssh keys
151
152     def start(self, delay=0):
153         if self.rspec['enabled'] > 0:
154             logger.log('%s: starting in %d seconds' % (self.name, delay))
155             time.sleep(delay)
156             child_pid = os.fork()
157             if child_pid == 0:
158                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
159                 tools.close_nonstandard_fds()
160                 vserver.VServer.start(self, True)
161                 os._exit(0)
162             else: os.waitpid(child_pid, 0)
163         else: logger.log('%s: not starting, is not enabled' % self.name)
164         self.initscriptchanged = False
165
166     def stop(self):
167         logger.log('%s: stopping' % self.name)
168         vserver.VServer.stop(self)
169
170     def set_resources(self):
171         disk_max = self.rspec['disk_max']
172         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
173         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
174             if not self.disk_usage_initialized:
175                 self.vm_running = False
176                 logger.log('%s: computing disk usage: beginning' % self.name)
177                 Sliver_VS._init_disk_info_sem.acquire()
178                 try: self.init_disk_info()
179                 finally: Sliver_VS._init_disk_info_sem.release()
180                 logger.log('%s: computing disk usage: ended' % self.name)
181                 self.disk_usage_initialized = True
182             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
183         except:
184             logger.log('%s: failed to set max disk usage' % self.name)
185             logger.log_exc(self.name)
186
187         # get/set the min/soft/hard values for all of the vserver
188         # related RLIMITS.  Note that vserver currently only
189         # implements support for hard limits.
190         for limit in vserver.RLIMITS.keys():
191             type = limit.lower()
192             minimum  = self.rspec['%s_min'%type]
193             soft = self.rspec['%s_soft'%type]
194             hard = self.rspec['%s_hard'%type]
195             update = self.set_rlimit(limit, hard, soft, minimum)
196             if update:
197                 logger.log('%s: setting rlimit %s to (%d, %d, %d)'
198                            % (self.name, type, hard, soft, minimum))
199
200         self.set_capabilities_config(self.rspec['capabilities'])
201         if self.rspec['capabilities']:
202             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
203
204         cpu_pct = self.rspec['cpu_pct']
205         cpu_share = self.rspec['cpu_share']
206
207         self.setname(rec['slice_id'])
208
209         if self.rspec['enabled'] > 0:
210             if cpu_pct > 0:
211                 logger.log('%s: setting cpu reservation to %d%%' % (self.name, cpu_pct))
212             else:
213                 cpu_pct = 0
214
215             if cpu_share > 0:
216                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
217             else:
218                 cpu_share = 0
219
220             self.set_sched_config(cpu_pct, cpu_share)
221             # if IP address isn't set (even to 0.0.0.0), sliver won't be able to use network
222             if self.rspec['ip_addresses'] != '0.0.0.0':
223                 logger.log('%s: setting IP address(es) to %s' % (self.name, self.rspec['ip_addresses']))
224             self.set_ipaddresses_config(self.rspec['ip_addresses'])
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()