fix slices that use vsys not deleted correctly
[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             etcpasswd = os.path.join(containerDir, 'etc/passwd')
162             if os.path.exists(etcpasswd):
163                 logger.log("adding user %s id %d to %s" % (name, uid, etcpasswd))
164                 file(etcpasswd,'a').write("%s:x:%d:%d::/home/%s:/bin/bash\n" % (name, uid, uid, name))
165
166         # Lookup for xid and create template after the user is created so we
167         # can get the correct xid based on the name of the slice
168         xid = bwlimit.get_xid(name)
169
170         # Template for libvirt sliver configuration
171         template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
172         if os.path.isfile (template_filename_sliceimage):
173             logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
174             template_filename=template_filename_sliceimage
175         else:
176             logger.log("Cannot find XML template %s"%template_filename_sliceimage)
177             return
178
179         interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
180
181         try:
182             with open(template_filename) as f:
183                 template = Template(f.read())
184                 xml  = template.substitute(name=name, interfaces=interfaces)
185         except IOError:
186             logger.log('Failed to parse or use XML template file %s'%template_filename)
187             return
188
189         # Lookup for the sliver before actually
190         # defining it, just in case it was already defined.
191         try:
192             dom = conn.lookupByName(name)
193         except:
194             dom = conn.defineXML(xml)
195         logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.debuginfo(dom)))
196
197
198     @staticmethod
199     def destroy(name):
200         logger.verbose ('sliver_lxc: %s destroy'%(name))
201         conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
202
203         containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
204
205         try:
206             # Destroy libvirt domain
207             dom = conn.lookupByName(name)
208         except:
209             logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
210
211         try:
212             dom.destroy()
213         except:
214             logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
215
216         try:
217             dom.undefine()
218         except:
219             logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
220
221         # Remove user after destroy domain to force logout
222         command = ['/usr/sbin/userdel', '-f', '-r', name]
223         logger.log_call(command, timeout=15*60)
224
225         if os.path.exists(os.path.join(containerDir,"vsys")):
226             # Slivers with vsys running will fail the subvolume delete.
227             # A more permanent solution may be to ensure that the vsys module
228             # is called before the sliver is destroyed.
229             logger.log("destroying vsys directory and restarting vsys")
230             logger.log_call(["rm", "-fR", os.path.join(containerDir, "vsys")])
231             logger.log_call(["/etc/init.d/vsys", "restart", ])
232
233         # Remove rootfs of destroyed domain
234         command = ['btrfs', 'subvolume', 'delete', containerDir]
235         logger.log_call(command, timeout=60)
236
237         if os.path.exists(containerDir):
238            # oh no, it's still here...
239            logger.log("WARNING: failed to destroy container %s" % containerDir)
240
241         logger.verbose('sliver_libvirt: %s destroyed.'%name)
242