add /bin and /sbin in path if not present in slivers - in root/.profile like for...
[nodemanager.git] / sliver_lxc.py
1 #
2
3 """LXC slivers"""
4
5 import subprocess
6 import sys
7 import time
8 import os, os.path
9 import grp
10 from pwd import getpwnam
11 from string import Template
12
13 import libvirt
14
15 import logger
16 import plnode.bwlimit as bwlimit
17 from initscript import Initscript
18 from sliver_libvirt import Sliver_Libvirt
19
20 class Sliver_LXC(Sliver_Libvirt, Initscript):
21     """This class wraps LXC commands"""
22
23     SHELL = '/usr/sbin/vsh'
24     TYPE = 'sliver.LXC'
25     # Need to add a tag at myplc to actually use this account
26     # type = 'sliver.LXC'
27
28     REF_IMG_BASE_DIR = '/vservers/.lvref'
29     CON_BASE_DIR     = '/vservers'
30
31     def __init__ (self, rec):
32         name=rec['name']
33         Sliver_Libvirt.__init__ (self,rec)
34         Initscript.__init__ (self,name)
35
36     def configure (self, rec):
37         Sliver_Libvirt.configure (self,rec)
38
39         # in case we update nodemanager..
40         self.install_and_enable_vinit()
41         # do the configure part from Initscript
42         Initscript.configure(self,rec)
43
44     def start(self, delay=0):
45         if 'enabled' in self.rspec and self.rspec['enabled'] <= 0:
46             logger.log('sliver_lxc: not starting %s, is not enabled'%self.name)
47             return
48         # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
49         self.install_and_enable_vinit()
50         Sliver_Libvirt.start (self, delay)
51         # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
52         self.refresh_slice_vinit()
53
54     def rerun_slice_vinit (self):
55         """This is called whenever the initscript code changes"""
56         # xxx - todo - not sure exactly how to:
57         # (.) invoke something in the guest
58         # (.) which options of systemctl should be used to trigger a restart
59         # should not prevent the first run from going fine hopefully
60         logger.log("WARNING: sliver_lxc.rerun_slice_vinit not implemented yet")
61
62     @staticmethod
63     def create(name, rec=None):
64         ''' Create dirs, copy fs image, lxc_create '''
65         logger.verbose ('sliver_lxc: %s create'%(name))
66         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
67
68         # Get the type of image from vref myplc tags specified as:
69         # pldistro = lxc
70         # fcdistro = squeeze
71         # arch x86_64
72         vref = rec['vref']
73         if vref is None:
74             logger.log('sliver_libvirt: %s: WARNING - no vref attached defaults to lxc-f14' % (name))
75             vref = "lxc-f14-x86_64"
76
77         refImgDir    = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR, vref)
78         containerDir = os.path.join(Sliver_LXC.CON_BASE_DIR, name)
79
80         # check the template exists -- there's probably a better way..
81         if not os.path.isdir(refImgDir):
82             logger.log('sliver_lxc: %s: ERROR Could not create sliver - reference image %s not found' % (name,vref))
83             logger.log('sliver_lxc: %s: ERROR Expected reference image in %s'%(name,refImgDir))
84             return
85
86         # Snapshot the reference image fs (assume the reference image is in its own
87         # subvolume)
88         command = ['btrfs', 'subvolume', 'snapshot', refImgDir, containerDir]
89         if not logger.log_call(command, timeout=15*60):
90             logger.log('sliver_lxc: ERROR Could not create BTRFS snapshot at', containerDir)
91             return
92         command = ['chmod', '755', containerDir]
93         logger.log_call(command, timeout=15*60)
94
95         # customize prompt for slice owner, + LD_PRELOAD for transparently wrap bind
96         dot_profile=os.path.join(containerDir,"root/.profile")
97         ld_preload_msg="""# by default, we define this setting so that calls to bind(2),
98 # when invoked on 0.0.0.0, get transparently redirected to the public interface of this node
99 # see https://svn.planet-lab.org/wiki/LxcPortForwarding"""
100         usrmove_path_msg="""# VM's before Features/UsrMove need /bin and /sbin in their PATH"""
101         usrmove_path_code="""
102 pathmunge () {
103         if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
104            if [ "$2" = "after" ] ; then
105               PATH=$PATH:$1
106            else
107               PATH=$1:$PATH
108            fi
109         fi
110 }
111 pathmunge /bin after
112 pathmunge /sbin after
113 unset pathmunge
114 """
115         with open(dot_profile,'w') as f:
116             f.write("export PS1='%s@\H \$ '\n"%(name))
117             f.write("%s\n"%ld_preload_msg)
118             f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
119             f.write("%s\n"%usrmove_path_msg)
120             f.write("%s\n"%usrmove_path_code)
121
122         # TODO: set quotas...
123
124         # Set hostname. A valid hostname cannot have '_'
125         #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
126         #    print >>f, name.replace('_', '-')
127
128         # Add slices group if not already present
129         try:
130             group = grp.getgrnam('slices')
131         except:
132             command = ['/usr/sbin/groupadd', 'slices']
133             logger.log_call(command, timeout=15*60)
134
135         # Add unix account (TYPE is specified in the subclass)
136         command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
137         logger.log_call(command, timeout=15*60)
138         command = ['mkdir', '/home/%s/.ssh'%name]
139         logger.log_call(command, timeout=15*60)
140
141         # Create PK pair keys to connect from the host to the guest without
142         # password... maybe remove the need for authentication inside the
143         # guest?
144         command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
145         logger.log_call(command, timeout=60)
146
147         command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
148         logger.log_call(command, timeout=30)
149
150         command = ['mkdir', '%s/root/.ssh'%containerDir]
151         logger.log_call(command, timeout=10)
152
153         command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
154         logger.log_call(command, timeout=30)
155
156         logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
157         try:
158             file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
159         except:
160             logger.log_exc("exception while creating /etc/slicename")
161
162         try:
163             file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
164         except:
165             logger.log_exc("exception while creating /etc/slicefamily")
166
167         uid = None
168         try:
169             uid = getpwnam(name).pw_uid
170         except KeyError:
171             # keyerror will happen if user id was not created successfully
172             logger.log_exc("exception while getting user id")
173
174         if uid is not None:
175             logger.log("uid is %d" % uid)
176             command = ['mkdir', '%s/home/%s' % (containerDir, name)]
177             logger.log_call(command, timeout=10)
178             command = ['chown', name, '%s/home/%s' % (containerDir, name)]
179             logger.log_call(command, timeout=10)
180             etcpasswd = os.path.join(containerDir, 'etc/passwd')
181             if os.path.exists(etcpasswd):
182                 logger.log("adding user %s id %d to %s" % (name, uid, etcpasswd))
183                 try:
184                     file(etcpasswd,'a').write("%s:x:%d:%d::/home/%s:/bin/bash\n" % (name, uid, uid, name))
185                 except:
186                     logger.log_exc("exception while updating etc/passwd")
187             sudoers = os.path.join(containerDir, 'etc/sudoers')
188             if os.path.exists(sudoers):
189                 try:
190                     file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
191                 except:
192                     logger.log_exc("exception while updating /etc/sudoers")
193
194         # Lookup for xid and create template after the user is created so we
195         # can get the correct xid based on the name of the slice
196         xid = bwlimit.get_xid(name)
197
198         # Template for libvirt sliver configuration
199         template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
200         if os.path.isfile (template_filename_sliceimage):
201             logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
202             template_filename=template_filename_sliceimage
203         else:
204             logger.log("Cannot find XML template %s"%template_filename_sliceimage)
205             return
206
207         interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
208
209         try:
210             with open(template_filename) as f:
211                 template = Template(f.read())
212                 xml  = template.substitute(name=name, interfaces=interfaces)
213         except IOError:
214             logger.log('Failed to parse or use XML template file %s'%template_filename)
215             return
216
217         # Lookup for the sliver before actually
218         # defining it, just in case it was already defined.
219         try:
220             dom = conn.lookupByName(name)
221         except:
222             dom = conn.defineXML(xml)
223         logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.debuginfo(dom)))
224
225
226     @staticmethod
227     def destroy(name):
228         logger.verbose ('sliver_lxc: %s destroy'%(name))
229         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
230
231         containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
232
233         try:
234             # Destroy libvirt domain
235             dom = conn.lookupByName(name)
236         except:
237             logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
238
239         try:
240             dom.destroy()
241         except:
242             logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
243
244         try:
245             dom.undefine()
246         except:
247             logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
248
249         # Remove user after destroy domain to force logout
250         command = ['/usr/sbin/userdel', '-f', '-r', name]
251         logger.log_call(command, timeout=15*60)
252
253         if os.path.exists(os.path.join(containerDir,"vsys")):
254             # Slivers with vsys running will fail the subvolume delete.
255             # A more permanent solution may be to ensure that the vsys module
256             # is called before the sliver is destroyed.
257             logger.log("destroying vsys directory and restarting vsys")
258             logger.log_call(["rm", "-fR", os.path.join(containerDir, "vsys")])
259             logger.log_call(["/etc/init.d/vsys", "restart", ])
260
261         # Remove rootfs of destroyed domain
262         command = ['btrfs', 'subvolume', 'delete', containerDir]
263         logger.log_call(command, timeout=60)
264
265         if os.path.exists(containerDir):
266            # oh no, it's still here...
267            logger.log("WARNING: failed to destroy container %s" % containerDir)
268
269         logger.verbose('sliver_libvirt: %s destroyed.'%name)
270