Add setting machine architecture for container
[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
73         arch = 'x86_64'
74         tags = rec['rspec']['tags']
75         if 'arch' in tags:
76             arch = tags['arch']
77             if arch == 'i386':
78                 arch = 'i686'
79
80         vref = rec['vref']
81         if vref is None:
82             logger.log('sliver_libvirt: %s: WARNING - no vref attached defaults to lxc-f14' % (name))
83             vref = "lxc-f14-x86_64"
84
85         refImgDir    = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR, vref)
86         containerDir = os.path.join(Sliver_LXC.CON_BASE_DIR, name)
87
88         # check the template exists -- there's probably a better way..
89         if not os.path.isdir(refImgDir):
90             logger.log('sliver_lxc: %s: ERROR Could not create sliver - reference image %s not found' % (name,vref))
91             logger.log('sliver_lxc: %s: ERROR Expected reference image in %s'%(name,refImgDir))
92             return
93
94         # Snapshot the reference image fs (assume the reference image is in its own
95         # subvolume)
96         command = ['btrfs', 'subvolume', 'snapshot', refImgDir, containerDir]
97         if not logger.log_call(command, timeout=15*60):
98             logger.log('sliver_lxc: ERROR Could not create BTRFS snapshot at', containerDir)
99             return
100         command = ['chmod', '755', containerDir]
101         logger.log_call(command, timeout=15*60)
102
103         # customize prompt for slice owner, + LD_PRELOAD for transparently wrap bind
104         dot_profile=os.path.join(containerDir,"root/.profile")
105         ld_preload_msg="""# by default, we define this setting so that calls to bind(2),
106 # when invoked on 0.0.0.0, get transparently redirected to the public interface of this node
107 # see https://svn.planet-lab.org/wiki/LxcPortForwarding"""
108         usrmove_path_msg="""# VM's before Features/UsrMove need /bin and /sbin in their PATH"""
109         usrmove_path_code="""
110 pathmunge () {
111         if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
112            if [ "$2" = "after" ] ; then
113               PATH=$PATH:$1
114            else
115               PATH=$1:$PATH
116            fi
117         fi
118 }
119 pathmunge /bin after
120 pathmunge /sbin after
121 unset pathmunge
122 """
123         with open(dot_profile,'w') as f:
124             f.write("export PS1='%s@\H \$ '\n"%(name))
125             f.write("%s\n"%ld_preload_msg)
126             f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
127             f.write("%s\n"%usrmove_path_msg)
128             f.write("%s\n"%usrmove_path_code)
129
130         # TODO: set quotas...
131
132         # Set hostname. A valid hostname cannot have '_'
133         #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
134         #    print >>f, name.replace('_', '-')
135
136         # Add slices group if not already present
137         try:
138             group = grp.getgrnam('slices')
139         except:
140             command = ['/usr/sbin/groupadd', 'slices']
141             logger.log_call(command, timeout=15*60)
142
143         # Add unix account (TYPE is specified in the subclass)
144         command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
145         logger.log_call(command, timeout=15*60)
146         command = ['mkdir', '/home/%s/.ssh'%name]
147         logger.log_call(command, timeout=15*60)
148
149         # Create PK pair keys to connect from the host to the guest without
150         # password... maybe remove the need for authentication inside the
151         # guest?
152         command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
153         logger.log_call(command, timeout=60)
154
155         command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
156         logger.log_call(command, timeout=30)
157
158         command = ['mkdir', '%s/root/.ssh'%containerDir]
159         logger.log_call(command, timeout=10)
160
161         command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
162         logger.log_call(command, timeout=30)
163
164         logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
165         try:
166             file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
167         except:
168             logger.log_exc("exception while creating /etc/slicename")
169
170         try:
171             file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
172         except:
173             logger.log_exc("exception while creating /etc/slicefamily")
174
175         uid = None
176         try:
177             uid = getpwnam(name).pw_uid
178         except KeyError:
179             # keyerror will happen if user id was not created successfully
180             logger.log_exc("exception while getting user id")
181
182         if uid is not None:
183             logger.log("uid is %d" % uid)
184             command = ['mkdir', '%s/home/%s' % (containerDir, name)]
185             logger.log_call(command, timeout=10)
186             command = ['chown', name, '%s/home/%s' % (containerDir, name)]
187             logger.log_call(command, timeout=10)
188             etcpasswd = os.path.join(containerDir, 'etc/passwd')
189             if os.path.exists(etcpasswd):
190                 logger.log("adding user %s id %d to %s" % (name, uid, etcpasswd))
191                 try:
192                     file(etcpasswd,'a').write("%s:x:%d:%d::/home/%s:/bin/bash\n" % (name, uid, uid, name))
193                 except:
194                     logger.log_exc("exception while updating etc/passwd")
195             sudoers = os.path.join(containerDir, 'etc/sudoers')
196             if os.path.exists(sudoers):
197                 try:
198                     file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
199                 except:
200                     logger.log_exc("exception while updating /etc/sudoers")
201
202         # Lookup for xid and create template after the user is created so we
203         # can get the correct xid based on the name of the slice
204         xid = bwlimit.get_xid(name)
205
206         # Template for libvirt sliver configuration
207         template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
208         if os.path.isfile (template_filename_sliceimage):
209             logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
210             template_filename=template_filename_sliceimage
211         else:
212             logger.log("Cannot find XML template %s"%template_filename_sliceimage)
213             return
214
215         interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
216
217         try:
218             with open(template_filename) as f:
219                 template = Template(f.read())
220                 xml  = template.substitute(name=name, interfaces=interfaces, arch=arch)
221         except IOError:
222             logger.log('Failed to parse or use XML template file %s'%template_filename)
223             return
224
225         # Lookup for the sliver before actually
226         # defining it, just in case it was already defined.
227         try:
228             dom = conn.lookupByName(name)
229         except:
230             dom = conn.defineXML(xml)
231         logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.debuginfo(dom)))
232
233
234     @staticmethod
235     def destroy(name):
236         logger.verbose ('sliver_lxc: %s destroy'%(name))
237         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
238
239         containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
240
241         try:
242             # Destroy libvirt domain
243             dom = conn.lookupByName(name)
244         except:
245             logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
246
247         try:
248             dom.destroy()
249         except:
250             logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
251
252         try:
253             dom.undefine()
254         except:
255             logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
256
257         # Remove user after destroy domain to force logout
258         command = ['/usr/sbin/userdel', '-f', '-r', name]
259         logger.log_call(command, timeout=15*60)
260
261         if os.path.exists(os.path.join(containerDir,"vsys")):
262             # Slivers with vsys running will fail the subvolume delete.
263             # A more permanent solution may be to ensure that the vsys module
264             # is called before the sliver is destroyed.
265             logger.log("destroying vsys directory and restarting vsys")
266             logger.log_call(["rm", "-fR", os.path.join(containerDir, "vsys")])
267             logger.log_call(["/etc/init.d/vsys", "restart", ])
268
269         # Remove rootfs of destroyed domain
270         command = ['btrfs', 'subvolume', 'delete', containerDir]
271         logger.log_call(command, timeout=60)
272
273         if os.path.exists(containerDir):
274            # oh no, it's still here...
275            logger.log("WARNING: failed to destroy container %s" % containerDir)
276
277         logger.verbose('sliver_libvirt: %s destroyed.'%name)
278