Add support for assigning IP addresses
[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
21 import threading
22 import time
23 import vserver
24
25 import accounts
26 import logger
27 import tools
28
29 # special constant that tells vserver to keep its existing settings
30 KEEP_LIMIT = vserver.VC_LIM_KEEP
31
32 # populate the sliver/vserver specific default allocations table,
33 # which is used to look for slice attributes
34 DEFAULT_ALLOCATION = {}
35 for rlimit in vserver.RLIMITS.keys():
36     rlim = rlimit.lower()
37     DEFAULT_ALLOCATION["%s_min"%rlim]=KEEP_LIMIT
38     DEFAULT_ALLOCATION["%s_soft"%rlim]=KEEP_LIMIT
39     DEFAULT_ALLOCATION["%s_hard"%rlim]=KEEP_LIMIT
40
41 class Sliver_VS(accounts.Account, vserver.VServer):
42     """This class wraps vserver.VServer to make its interface closer to what we need."""
43
44     SHELL = '/bin/vsh'
45     TYPE = 'sliver.VServer'
46     _init_disk_info_sem = threading.Semaphore(1)
47
48     def __init__(self, rec):
49         try:
50             vserver.VServer.__init__(self, rec['name'])
51         except Exception, err:
52             if not isinstance(err, vserver.NoSuchVServer):
53                 # Probably a bad vserver or vserver configuration file
54                 logger.log_exc()
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'])
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         if vref is not None:
70             logger.log_call('/usr/sbin/vuseradd', '-t', vref, name)
71         else:
72             logger.log_call('/usr/sbin/vuseradd', name)
73         open('/vservers/%s/etc/slicename' % name, 'w').write(name)
74
75     @staticmethod
76     def destroy(name): logger.log_call('/usr/sbin/vuserdel', name)
77
78     def configure(self, rec):
79         new_rspec = rec['_rspec']
80         if new_rspec != self.rspec:
81             self.rspec = new_rspec
82             self.set_resources()
83
84         new_initscript = rec['initscript']
85         if new_initscript != self.initscript:
86             self.initscript = new_initscript
87             logger.log('%s: installing initscript' % self.name)
88             def install_initscript():
89                 flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
90                 fd = os.open('/etc/rc.vinit', flags, 0755)
91                 os.write(fd, new_initscript)
92                 os.close(fd)
93             try:
94                 self.chroot_call(install_initscript)
95                 self.initscriptchanged = True
96             except: logger.log_exc()
97
98         accounts.Account.configure(self, rec)  # install ssh keys
99
100     def start(self, delay=0):
101         if self.rspec['enabled'] > 0:
102             logger.log('%s: starting in %d seconds' % (self.name, delay))
103             time.sleep(delay)
104             child_pid = os.fork()
105             if child_pid == 0:
106                 # VServer.start calls fork() internally, so just close the nonstandard fds and fork once to avoid creating zombies
107                 tools.close_nonstandard_fds()
108                 vserver.VServer.start(self, True)
109                 os._exit(0)
110             else: os.waitpid(child_pid, 0)
111         else: logger.log('%s: not starting, is not enabled' % self.name)
112         self.initscriptchanged = False
113
114     def stop(self):
115         logger.log('%s: stopping' % self.name)
116         vserver.VServer.stop(self)
117
118     def set_resources(self):
119         disk_max = self.rspec['disk_max']
120         logger.log('%s: setting max disk usage to %d KiB' % (self.name, disk_max))
121         try:  # if the sliver is over quota, .set_disk_limit will throw an exception
122             if not self.disk_usage_initialized:
123                 self.vm_running = False
124                 logger.log('%s: computing disk usage: beginning' % self.name)
125                 Sliver_VS._init_disk_info_sem.acquire()
126                 try: self.init_disk_info()
127                 finally: Sliver_VS._init_disk_info_sem.release()
128                 logger.log('%s: computing disk usage: ended' % self.name)
129                 self.disk_usage_initialized = True
130             vserver.VServer.set_disklimit(self, max(disk_max, self.disk_blocks))
131         except OSError:
132             logger.log('%s: failed to set max disk usage' % self.name)
133             logger.log_exc()
134
135         # get/set the min/soft/hard values for all of the vserver
136         # related RLIMITS.  Note that vserver currently only
137         # implements support for hard limits.
138         for limit in vserver.RLIMITS.keys():
139             type = limit.lower()
140             minimum  = self.rspec['%s_min'%type]
141             soft = self.rspec['%s_soft'%type]
142             hard = self.rspec['%s_hard'%type]
143             self.set_rlimit_config(limit, hard, soft, minimum)
144
145         self.set_capabilities_config(self.rspec['capabilities'])
146         if self.rspec['capabilities']:
147             logger.log('%s: setting capabilities to %s' % (self.name, self.rspec['capabilities']))
148
149         if False: # this code was commented out before
150             # N.B. net_*_rate are in kbps because of XML-RPC maxint
151             # limitations, convert to bps which is what bwlimit.py expects.
152             net_limits = (self.rspec['net_min_rate'] * 1000,
153                           self.rspec['net_max_rate'] * 1000,
154                           self.rspec['net_i2_min_rate'] * 1000,
155                           self.rspec['net_i2_max_rate'] * 1000,
156                           self.rspec['net_share'])
157             logger.log('%s: setting net limits to %s bps' % (self.name, net_limits[:-1]))
158             logger.log('%s: setting net share to %d' % (self.name, net_limits[-1]))
159             self.set_bwlimit(*net_limits)
160
161         cpu_min = self.rspec['cpu_min']
162         cpu_share = self.rspec['cpu_share']
163
164         if self.rspec['enabled'] > 0:
165             if cpu_min >= 50:  # at least 5%: keep people from shooting themselves in the foot
166                 logger.log('%s: setting cpu share to %d%% guaranteed' % (self.name, cpu_min/10.0))
167                 self.set_sched_config(cpu_min, vserver.SCHED_CPU_GUARANTEED)
168             else:
169                 logger.log('%s: setting cpu share to %d' % (self.name, cpu_share))
170                 self.set_sched_config(cpu_share, 0)
171
172             if self.rspec['ip_addresses'] != '0.0.0.0':
173                 logger.log('%s: setting IP address(es) to %s' % (self.name, self.rspec['ip_addresses']))
174             self.set_ipaddresses_config(self.rspec['ip_addresses'])
175
176             if False: # Does not work properly yet.
177                 if self.have_limits_changed():
178                     logger.log('%s: limits have changed --- restarting' % self.name)
179                     stopcount = 10
180                     while self.is_running() and stopcount > 0:
181                         self.stop()
182                         delay = 1
183                         time.sleep(delay)
184                         stopcount = stopcount - 1
185                     self.start()
186
187         else:  # tell vsh to disable remote login by setting CPULIMIT to 0
188             logger.log('%s: disabling remote login' % self.name)
189             self.set_sched_config(0, 0)
190             self.stop()