10 from pwd import getpwnam
11 from string import Template
13 # vsys probably should not be a plugin
14 # the thing is, the right way to handle stuff would be that
15 # if slivers get created by doing a,b,c
16 # then they sohuld be delted by doing c,b,a
17 # the current ordering model for vsys plugins completely fails to capture that
18 from plugins.vsys import removeSliverFromVsys, startService as vsysStartService
23 import plnode.bwlimit as bwlimit
24 from initscript import Initscript
25 from account import Account
26 from sliver_libvirt import Sliver_Libvirt
30 class Sliver_LXC(Sliver_Libvirt, Initscript):
31 """This class wraps LXC commands"""
33 SHELL = '/usr/sbin/vsh'
35 # Need to add a tag at myplc to actually use this account
38 REF_IMG_BASE_DIR = '/vservers/.lvref'
39 CON_BASE_DIR = '/vservers'
41 def __init__ (self, rec):
43 Sliver_Libvirt.__init__ (self,rec)
44 Initscript.__init__ (self,name)
46 def configure (self, rec):
47 Sliver_Libvirt.configure (self,rec)
49 # in case we update nodemanager..
50 self.install_and_enable_vinit()
51 # do the configure part from Initscript
52 Initscript.configure(self,rec)
54 def start(self, delay=0):
55 if 'enabled' in self.rspec and self.rspec['enabled'] <= 0:
56 logger.log('sliver_lxc: not starting %s, is not enabled'%self.name)
58 # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
59 self.install_and_enable_vinit()
60 # expose .ssh for omf_friendly slivers
61 if 'tags' in self.rspec and 'omf_control' in self.rspec['tags']:
62 Account.mount_ssh_dir(self.name)
63 Sliver_Libvirt.start (self, delay)
64 # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
65 self.refresh_slice_vinit()
67 def rerun_slice_vinit (self):
68 """This is called whenever the initscript code changes"""
69 # xxx - todo - not sure exactly how to:
70 # (.) invoke something in the guest
71 # (.) which options of systemctl should be used to trigger a restart
72 # should not prevent the first run from going fine hopefully
73 logger.log("WARNING: sliver_lxc.rerun_slice_vinit not implemented yet")
76 def create(name, rec=None):
77 ''' Create dirs, copy fs image, lxc_create '''
78 logger.verbose ('sliver_lxc: %s create'%(name))
79 conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
81 # Get the type of image from vref myplc tags specified as:
87 tags = rec['rspec']['tags']
95 vref = "lxc-f18-x86_64"
96 logger.log("sliver_libvirt: %s: WARNING - no vref attached, using hard-wired default %s" % (name,vref))
98 refImgDir = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR, vref)
99 containerDir = os.path.join(Sliver_LXC.CON_BASE_DIR, name)
101 # check the template exists -- there's probably a better way..
102 if not os.path.isdir(refImgDir):
103 logger.log('sliver_lxc: %s: ERROR Could not create sliver - reference image %s not found' % (name,vref))
104 logger.log('sliver_lxc: %s: ERROR Expected reference image in %s'%(name,refImgDir))
107 # this hopefully should be fixed now
108 # # in fedora20 we have some difficulty in properly cleaning up /vservers/<slicename>
109 # # also note that running e.g. btrfs subvolume create /vservers/.lvref/image /vservers/foo
110 # # behaves differently, whether /vservers/foo exists or not:
111 # # if /vservers/foo does not exist, it creates /vservers/foo
112 # # but if it does exist, then it creates /vservers/foo/image !!
113 # # so we need to check the expected container rootfs does not exist yet
114 # # this hopefully could be removed in a future release
115 # if os.path.exists (containerDir):
116 # logger.log("sliver_lxc: %s: WARNING cleaning up pre-existing %s"%(name,containerDir))
117 # command = ['btrfs', 'subvolume', 'delete', containerDir]
118 # logger.log_call(command, BTRFS_TIMEOUT)
120 # if os.path.exists (containerDir):
121 # logger.log('sliver_lxc: %s: ERROR Could not create sliver - could not clean up empty %s'%(name,containerDir))
124 # Snapshot the reference image fs (assume the reference image is in its own
126 command = ['btrfs', 'subvolume', 'snapshot', refImgDir, containerDir]
127 if not logger.log_call(command, timeout=BTRFS_TIMEOUT):
128 logger.log('sliver_lxc: ERROR Could not create BTRFS snapshot at', containerDir)
130 command = ['chmod', '755', containerDir]
131 logger.log_call(command)
133 # TODO: set quotas...
135 # Set hostname. A valid hostname cannot have '_'
136 #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
137 # print >>f, name.replace('_', '-')
139 # Add slices group if not already present
141 group = grp.getgrnam('slices')
143 command = ['/usr/sbin/groupadd', 'slices']
144 logger.log_call(command)
146 # Add unix account (TYPE is specified in the subclass)
147 command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
148 logger.log_call(command)
149 command = ['mkdir', '/home/%s/.ssh'%name]
150 logger.log_call(command)
152 # Create PK pair keys to connect from the host to the guest without
153 # password... maybe remove the need for authentication inside the
155 command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
156 logger.log_call(command)
158 command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
159 logger.log_call(command)
161 command = ['mkdir', '%s/root/.ssh'%containerDir]
162 logger.log_call(command)
164 command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
165 logger.log_call(command)
167 logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
169 file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
171 logger.log_exc("exception while creating /etc/slicename")
174 file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
176 logger.log_exc("exception while creating /etc/slicefamily")
180 uid = getpwnam(name).pw_uid
182 # keyerror will happen if user id was not created successfully
183 logger.log_exc("exception while getting user id")
186 logger.log("uid is %d" % uid)
187 command = ['mkdir', '%s/home/%s' % (containerDir, name)]
188 logger.log_call(command)
189 command = ['chown', name, '%s/home/%s' % (containerDir, name)]
190 logger.log_call(command)
191 etcpasswd = os.path.join(containerDir, 'etc/passwd')
192 etcgroup = os.path.join(containerDir, 'etc/group')
193 if os.path.exists(etcpasswd):
194 # create all accounts with gid=1001 - i.e. 'slices' like it is in the root context
196 logger.log("adding user %(name)s id %(uid)d gid %(slices_gid)d to %(etcpasswd)s" % (locals()))
198 file(etcpasswd,'a').write("%(name)s:x:%(uid)d:%(slices_gid)d::/home/%(name)s:/bin/bash\n" % locals())
200 logger.log_exc("exception while updating %s"%etcpasswd)
201 logger.log("adding group slices with gid %(slices_gid)d to %(etcgroup)s"%locals())
203 file(etcgroup,'a').write("slices:x:%(slices_gid)d\n"%locals())
205 logger.log_exc("exception while updating %s"%etcgroup)
206 sudoers = os.path.join(containerDir, 'etc/sudoers')
207 if os.path.exists(sudoers):
209 file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
211 logger.log_exc("exception while updating /etc/sudoers")
213 # customizations for the user environment - root or slice uid
214 # we save the whole business in /etc/planetlab.profile
215 # and source this file for both root and the slice uid's .profile
216 # prompt for slice owner, + LD_PRELOAD for transparently wrap bind
217 pl_profile=os.path.join(containerDir,"etc/planetlab.profile")
218 ld_preload_text="""# by default, we define this setting so that calls to bind(2),
219 # when invoked on 0.0.0.0, get transparently redirected to the public interface of this node
220 # see https://svn.planet-lab.org/wiki/LxcPortForwarding"""
221 usrmove_path_text="""# VM's before Features/UsrMove need /bin and /sbin in their PATH"""
222 usrmove_path_code="""
224 if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
225 if [ "$2" = "after" ] ; then
233 pathmunge /sbin after
236 with open(pl_profile,'w') as f:
237 f.write("export PS1='%s@\H \$ '\n"%(name))
238 f.write("%s\n"%ld_preload_text)
239 f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
240 f.write("%s\n"%usrmove_path_text)
241 f.write("%s\n"%usrmove_path_code)
243 # make sure this file is sourced from both root's and slice's .profile
244 enforced_line = "[ -f /etc/planetlab.profile ] && source /etc/planetlab.profile\n"
245 for path in [ 'root/.profile', 'home/%s/.profile'%name ]:
246 from_root=os.path.join(containerDir,path)
247 # if dir is not yet existing let's forget it for now
248 if not os.path.isdir(os.path.dirname(from_root)): continue
251 contents=file(from_root).readlines()
252 for content in contents:
253 if content==enforced_line: found=True
256 with open(from_root,"a") as user_profile:
257 user_profile.write(enforced_line)
258 # in case we create the slice's .profile when writing
259 if from_root.find("/home")>=0:
260 command=['chown','%s:slices'%name,from_root]
261 logger.log_call(command)
263 # Lookup for xid and create template after the user is created so we
264 # can get the correct xid based on the name of the slice
265 xid = bwlimit.get_xid(name)
267 # Template for libvirt sliver configuration
268 template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
269 if os.path.isfile (template_filename_sliceimage):
270 logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
271 template_filename=template_filename_sliceimage
273 logger.log("Cannot find XML template %s"%template_filename_sliceimage)
276 interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
279 with open(template_filename) as f:
280 template = Template(f.read())
281 xml = template.substitute(name=name, xid=xid, interfaces=interfaces, arch=arch)
283 logger.log('Failed to parse or use XML template file %s'%template_filename)
286 # Lookup for the sliver before actually
287 # defining it, just in case it was already defined.
289 dom = conn.lookupByName(name)
291 dom = conn.defineXML(xml)
292 logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.dom_details(dom)))
297 # umount .ssh directory - only if mounted
298 Account.umount_ssh_dir(name)
299 logger.verbose ('sliver_lxc: %s destroy'%(name))
300 conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
302 containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
305 # Destroy libvirt domain
306 dom = conn.lookupByName(name)
308 logger.verbose('sliver_lxc.destroy: Domain %s does not exist!' % name)
311 # Slivers with vsys running will fail the subvolume delete
312 # removeSliverFromVsys return True if it stops vsys, telling us to start it again later
313 vsys_stopped = removeSliverFromVsys (name)
316 logger.log("sliver_lxc.destroy: destroying domain %s"%name)
319 logger.verbose('sliver_lxc.destroy: Domain %s not running... continuing.' % name)
322 logger.log("sliver_lxc.destroy: undefining domain %s"%name)
325 logger.verbose('sliver_lxc.destroy: Domain %s is not defined... continuing.' % name)
327 # Remove user after destroy domain to force logout
328 command = ['/usr/sbin/userdel', '-f', '-r', name]
329 logger.log_call(command)
331 # Remove rootfs of destroyed domain
332 command = ['btrfs', 'subvolume', 'delete', containerDir]
333 logger.log_call(command, timeout=BTRFS_TIMEOUT)
335 # For some reason I am seeing this :
336 #log_call: running command btrfs subvolume delete /vservers/inri_sl1
337 #log_call: ERROR: cannot delete '/vservers/inri_sl1' - Device or resource busy
338 #log_call: Delete subvolume '/vservers/inri_sl1'
339 #log_call:end command (btrfs subvolume delete /vservers/inri_sl1) returned with code 1
341 # something must have an open handle to a file in there, but I can't find out what it is
342 # the following code aims at gathering data on what is going on in the system at this point in time
343 # note that some time later (typically when the sliver gets re-created) the same
344 # attempt at deleting the subvolume does work
345 # also lsof never shows anything relevant; this is painful..
347 if not os.path.exists(containerDir):
348 logger.log('sliver_lxc.destroy: %s cleanly destroyed.'%name)
351 #logger.log("-TMP-cwd %s : %s"%(name,os.getcwd()))
352 # also lsof never shows anything relevant; this is painful..
353 #logger.log("-TMP-lsof %s"%name)
355 #logger.log_call(command)
356 logger.log("-TMP-ls-l %s"%name)
357 command = ['ls', '-l', containerDir]
358 logger.log_call(command)
359 if os.path.exists(containerDir):
360 logger.log('sliver_lxc.destroy: ERROR could not cleanly destroy %s - giving up'%name)
362 if vsys_stopped: vsysStartService()