fix perms of slice homedir, add slice user to /etc/sudoers inside of slice
[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 = ['chown', name, '%s/root/.ssh'%containerDir]
137         logger.log_call(command, timeout=10)
138
139         command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
140         logger.log_call(command, timeout=30)
141
142         logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
143         try:
144             file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
145         except:
146             logger.log_exc("exception while creating /etc/slicename")
147
148         try:
149             file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
150         except:
151             logger.log_exc("exception while creating /etc/slicefamily")
152
153         uid = None
154         try:
155             uid = getpwnam(name).pw_uid
156         except KeyError:
157             # keyerror will happen if user id was not created successfully
158             logger.log_exc("exception while getting user id")
159
160         if uid is not None:
161             logger.log("uid is %d" % uid)
162             command = ['mkdir', '%s/home/%s' % (containerDir, name)]
163             logger.log_call(command, timeout=10)
164             command = ['chown', name, '%s/home/%s' % (containerDir, name)]
165             logger.log_call(command, timeout=10)
166             etcpasswd = os.path.join(containerDir, 'etc/passwd')
167             if os.path.exists(etcpasswd):
168                 logger.log("adding user %s id %d to %s" % (name, uid, etcpasswd))
169                 file(etcpasswd,'a').write("%s:x:%d:%d::/home/%s:/bin/bash\n" % (name, uid, uid, name))
170             sudoers = os.path.join(containerDir, 'etc/sudoers')
171             if os.path.exists(sudoers):
172                 file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
173
174         # Lookup for xid and create template after the user is created so we
175         # can get the correct xid based on the name of the slice
176         xid = bwlimit.get_xid(name)
177
178         # Template for libvirt sliver configuration
179         template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
180         if os.path.isfile (template_filename_sliceimage):
181             logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
182             template_filename=template_filename_sliceimage
183         else:
184             logger.log("Cannot find XML template %s"%template_filename_sliceimage)
185             return
186
187         interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
188
189         try:
190             with open(template_filename) as f:
191                 template = Template(f.read())
192                 xml  = template.substitute(name=name, interfaces=interfaces)
193         except IOError:
194             logger.log('Failed to parse or use XML template file %s'%template_filename)
195             return
196
197         # Lookup for the sliver before actually
198         # defining it, just in case it was already defined.
199         try:
200             dom = conn.lookupByName(name)
201         except:
202             dom = conn.defineXML(xml)
203         logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.debuginfo(dom)))
204
205
206     @staticmethod
207     def destroy(name):
208         logger.verbose ('sliver_lxc: %s destroy'%(name))
209         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
210
211         containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
212
213         try:
214             # Destroy libvirt domain
215             dom = conn.lookupByName(name)
216         except:
217             logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
218
219         try:
220             dom.destroy()
221         except:
222             logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
223
224         try:
225             dom.undefine()
226         except:
227             logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
228
229         # Remove user after destroy domain to force logout
230         command = ['/usr/sbin/userdel', '-f', '-r', name]
231         logger.log_call(command, timeout=15*60)
232
233         if os.path.exists(os.path.join(containerDir,"vsys")):
234             # Slivers with vsys running will fail the subvolume delete.
235             # A more permanent solution may be to ensure that the vsys module
236             # is called before the sliver is destroyed.
237             logger.log("destroying vsys directory and restarting vsys")
238             logger.log_call(["rm", "-fR", os.path.join(containerDir, "vsys")])
239             logger.log_call(["/etc/init.d/vsys", "restart", ])
240
241         # Remove rootfs of destroyed domain
242         command = ['btrfs', 'subvolume', 'delete', containerDir]
243         logger.log_call(command, timeout=60)
244
245         if os.path.exists(containerDir):
246            # oh no, it's still here...
247            logger.log("WARNING: failed to destroy container %s" % containerDir)
248
249         logger.verbose('sliver_libvirt: %s destroyed.'%name)
250