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