some exception handling around the etc/passwd and etc/sudoers just to be safe
[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', containDir)
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         with open(dot_profile,'w') as f:
101             f.write("export PS1='%s@\H \$ '\n"%(name))
102             f.write("%s\n"%ld_preload_msg)
103             f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
104
105         # TODO: set quotas...
106
107         # Set hostname. A valid hostname cannot have '_'
108         #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
109         #    print >>f, name.replace('_', '-')
110
111         # Add slices group if not already present
112         try:
113             group = grp.getgrnam('slices')
114         except:
115             command = ['/usr/sbin/groupadd', 'slices']
116             logger.log_call(command, timeout=15*60)
117
118         # Add unix account (TYPE is specified in the subclass)
119         command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
120         logger.log_call(command, timeout=15*60)
121         command = ['mkdir', '/home/%s/.ssh'%name]
122         logger.log_call(command, timeout=15*60)
123
124         # Create PK pair keys to connect from the host to the guest without
125         # password... maybe remove the need for authentication inside the
126         # guest?
127         command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
128         logger.log_call(command, timeout=60)
129
130         command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
131         logger.log_call(command, timeout=30)
132
133         command = ['mkdir', '%s/root/.ssh'%containerDir]
134         logger.log_call(command, timeout=10)
135
136         command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
137         logger.log_call(command, timeout=30)
138
139         logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
140         try:
141             file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
142         except:
143             logger.log_exc("exception while creating /etc/slicename")
144
145         try:
146             file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
147         except:
148             logger.log_exc("exception while creating /etc/slicefamily")
149
150         uid = None
151         try:
152             uid = getpwnam(name).pw_uid
153         except KeyError:
154             # keyerror will happen if user id was not created successfully
155             logger.log_exc("exception while getting user id")
156
157         if uid is not None:
158             logger.log("uid is %d" % uid)
159             command = ['mkdir', '%s/home/%s' % (containerDir, name)]
160             logger.log_call(command, timeout=10)
161             command = ['chown', name, '%s/home/%s' % (containerDir, name)]
162             logger.log_call(command, timeout=10)
163             etcpasswd = os.path.join(containerDir, 'etc/passwd')
164             if os.path.exists(etcpasswd):
165                 logger.log("adding user %s id %d to %s" % (name, uid, etcpasswd))
166                 try:
167                     file(etcpasswd,'a').write("%s:x:%d:%d::/home/%s:/bin/bash\n" % (name, uid, uid, name))
168                 except:
169                     logger.log_exc("exception while updating etc/passwd")
170             sudoers = os.path.join(containerDir, 'etc/sudoers')
171             if os.path.exists(sudoers):
172                 try:
173                     file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
174                 except:
175                     logger.log_exc("exception while updating /etc/sudoers")
176
177         # Lookup for xid and create template after the user is created so we
178         # can get the correct xid based on the name of the slice
179         xid = bwlimit.get_xid(name)
180
181         # Template for libvirt sliver configuration
182         template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
183         if os.path.isfile (template_filename_sliceimage):
184             logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
185             template_filename=template_filename_sliceimage
186         else:
187             logger.log("Cannot find XML template %s"%template_filename_sliceimage)
188             return
189
190         interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
191
192         try:
193             with open(template_filename) as f:
194                 template = Template(f.read())
195                 xml  = template.substitute(name=name, interfaces=interfaces)
196         except IOError:
197             logger.log('Failed to parse or use XML template file %s'%template_filename)
198             return
199
200         # Lookup for the sliver before actually
201         # defining it, just in case it was already defined.
202         try:
203             dom = conn.lookupByName(name)
204         except:
205             dom = conn.defineXML(xml)
206         logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.debuginfo(dom)))
207
208
209     @staticmethod
210     def destroy(name):
211         logger.verbose ('sliver_lxc: %s destroy'%(name))
212         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
213
214         containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
215
216         try:
217             # Destroy libvirt domain
218             dom = conn.lookupByName(name)
219         except:
220             logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
221
222         try:
223             dom.destroy()
224         except:
225             logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
226
227         try:
228             dom.undefine()
229         except:
230             logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
231
232         # Remove user after destroy domain to force logout
233         command = ['/usr/sbin/userdel', '-f', '-r', name]
234         logger.log_call(command, timeout=15*60)
235
236         if os.path.exists(os.path.join(containerDir,"vsys")):
237             # Slivers with vsys running will fail the subvolume delete.
238             # A more permanent solution may be to ensure that the vsys module
239             # is called before the sliver is destroyed.
240             logger.log("destroying vsys directory and restarting vsys")
241             logger.log_call(["rm", "-fR", os.path.join(containerDir, "vsys")])
242             logger.log_call(["/etc/init.d/vsys", "restart", ])
243
244         # Remove rootfs of destroyed domain
245         command = ['btrfs', 'subvolume', 'delete', containerDir]
246         logger.log_call(command, timeout=60)
247
248         if os.path.exists(containerDir):
249            # oh no, it's still here...
250            logger.log("WARNING: failed to destroy container %s" % containerDir)
251
252         logger.verbose('sliver_libvirt: %s destroyed.'%name)
253