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 # in fedora20 we have some difficulty in properly cleaning up /vservers/<slicename>
108 # also note that running e.g. btrfs subvolume create /vservers/.lvref/image /vservers/foo
109 # behaves differently, whether /vservers/foo exists or not:
110 # if /vservers/foo does not exist, it creates /vservers/foo
111 # but if it does exist, then it creates /vservers/foo/image !!
112 # so we need to check the expected container rootfs does not exist yet
113 # this hopefully could be removed in a future release
114 if os.path.exists (containerDir):
115 logger.log("sliver_lxc: %s: WARNING cleaning up pre-existing %s"%(name,containerDir))
116 command = ['btrfs', 'subvolume', 'delete', containerDir]
117 logger.log_call(command, BTRFS_TIMEOUT)
119 if os.path.exists (containerDir):
120 logger.log('sliver_lxc: %s: ERROR Could not create sliver - could not clean up empty %s'%(name,containerDir))
123 # Snapshot the reference image fs (assume the reference image is in its own
125 command = ['btrfs', 'subvolume', 'snapshot', refImgDir, containerDir]
126 if not logger.log_call(command, timeout=BTRFS_TIMEOUT):
127 logger.log('sliver_lxc: ERROR Could not create BTRFS snapshot at', containerDir)
129 command = ['chmod', '755', containerDir]
130 logger.log_call(command)
132 # TODO: set quotas...
134 # Set hostname. A valid hostname cannot have '_'
135 #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
136 # print >>f, name.replace('_', '-')
138 # Add slices group if not already present
140 group = grp.getgrnam('slices')
142 command = ['/usr/sbin/groupadd', 'slices']
143 logger.log_call(command)
145 # Add unix account (TYPE is specified in the subclass)
146 command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
147 logger.log_call(command)
148 command = ['mkdir', '/home/%s/.ssh'%name]
149 logger.log_call(command)
151 # Create PK pair keys to connect from the host to the guest without
152 # password... maybe remove the need for authentication inside the
154 command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
155 logger.log_call(command)
157 command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
158 logger.log_call(command)
160 command = ['mkdir', '%s/root/.ssh'%containerDir]
161 logger.log_call(command)
163 command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
164 logger.log_call(command)
166 logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
168 file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
170 logger.log_exc("exception while creating /etc/slicename")
173 file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
175 logger.log_exc("exception while creating /etc/slicefamily")
179 uid = getpwnam(name).pw_uid
181 # keyerror will happen if user id was not created successfully
182 logger.log_exc("exception while getting user id")
185 logger.log("uid is %d" % uid)
186 command = ['mkdir', '%s/home/%s' % (containerDir, name)]
187 logger.log_call(command)
188 command = ['chown', name, '%s/home/%s' % (containerDir, name)]
189 logger.log_call(command)
190 etcpasswd = os.path.join(containerDir, 'etc/passwd')
191 etcgroup = os.path.join(containerDir, 'etc/group')
192 if os.path.exists(etcpasswd):
193 # create all accounts with gid=1001 - i.e. 'slices' like it is in the root context
195 logger.log("adding user %(name)s id %(uid)d gid %(slices_gid)d to %(etcpasswd)s" % (locals()))
197 file(etcpasswd,'a').write("%(name)s:x:%(uid)d:%(slices_gid)d::/home/%(name)s:/bin/bash\n" % locals())
199 logger.log_exc("exception while updating %s"%etcpasswd)
200 logger.log("adding group slices with gid %(slices_gid)d to %(etcgroup)s"%locals())
202 file(etcgroup,'a').write("slices:x:%(slices_gid)d\n"%locals())
204 logger.log_exc("exception while updating %s"%etcgroup)
205 sudoers = os.path.join(containerDir, 'etc/sudoers')
206 if os.path.exists(sudoers):
208 file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
210 logger.log_exc("exception while updating /etc/sudoers")
212 # customizations for the user environment - root or slice uid
213 # we save the whole business in /etc/planetlab.profile
214 # and source this file for both root and the slice uid's .profile
215 # prompt for slice owner, + LD_PRELOAD for transparently wrap bind
216 pl_profile=os.path.join(containerDir,"etc/planetlab.profile")
217 ld_preload_text="""# by default, we define this setting so that calls to bind(2),
218 # when invoked on 0.0.0.0, get transparently redirected to the public interface of this node
219 # see https://svn.planet-lab.org/wiki/LxcPortForwarding"""
220 usrmove_path_text="""# VM's before Features/UsrMove need /bin and /sbin in their PATH"""
221 usrmove_path_code="""
223 if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
224 if [ "$2" = "after" ] ; then
232 pathmunge /sbin after
235 with open(pl_profile,'w') as f:
236 f.write("export PS1='%s@\H \$ '\n"%(name))
237 f.write("%s\n"%ld_preload_text)
238 f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
239 f.write("%s\n"%usrmove_path_text)
240 f.write("%s\n"%usrmove_path_code)
242 # make sure this file is sourced from both root's and slice's .profile
243 enforced_line = "[ -f /etc/planetlab.profile ] && source /etc/planetlab.profile\n"
244 for path in [ 'root/.profile', 'home/%s/.profile'%name ]:
245 from_root=os.path.join(containerDir,path)
246 # if dir is not yet existing let's forget it for now
247 if not os.path.isdir(os.path.dirname(from_root)): continue
250 contents=file(from_root).readlines()
251 for content in contents:
252 if content==enforced_line: found=True
255 with open(from_root,"a") as user_profile:
256 user_profile.write(enforced_line)
257 # in case we create the slice's .profile when writing
258 if from_root.find("/home")>=0:
259 command=['chown','%s:slices'%name,from_root]
260 logger.log_call(command)
262 # Lookup for xid and create template after the user is created so we
263 # can get the correct xid based on the name of the slice
264 xid = bwlimit.get_xid(name)
266 # Template for libvirt sliver configuration
267 template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
268 if os.path.isfile (template_filename_sliceimage):
269 logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
270 template_filename=template_filename_sliceimage
272 logger.log("Cannot find XML template %s"%template_filename_sliceimage)
275 interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
278 with open(template_filename) as f:
279 template = Template(f.read())
280 xml = template.substitute(name=name, xid=xid, interfaces=interfaces, arch=arch)
282 logger.log('Failed to parse or use XML template file %s'%template_filename)
285 # Lookup for the sliver before actually
286 # defining it, just in case it was already defined.
288 dom = conn.lookupByName(name)
290 dom = conn.defineXML(xml)
291 logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.dom_details(dom)))
296 # umount .ssh directory - only if mounted
297 Account.umount_ssh_dir(name)
298 logger.verbose ('sliver_lxc: %s destroy'%(name))
299 conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
301 containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
304 # Destroy libvirt domain
305 dom = conn.lookupByName(name)
307 logger.verbose('sliver_lxc.destroy: Domain %s does not exist!' % name)
310 # Slivers with vsys running will fail the subvolume delete
311 # removeSliverFromVsys return True if it stops vsys, telling us to start it again later
312 vsys_stopped = removeSliverFromVsys (name)
315 logger.log("sliver_lxc.destroy: destroying domain %s"%name)
318 logger.verbose('sliver_lxc.destroy: Domain %s not running... continuing.' % name)
321 logger.log("sliver_lxc.destroy: undefining domain %s"%name)
324 logger.verbose('sliver_lxc.destroy: Domain %s is not defined... continuing.' % name)
326 # Remove user after destroy domain to force logout
327 command = ['/usr/sbin/userdel', '-f', '-r', name]
328 logger.log_call(command)
330 # Remove rootfs of destroyed domain
331 command = ['btrfs', 'subvolume', 'delete', containerDir]
332 logger.log_call(command, timeout=BTRFS_TIMEOUT)
334 # For some reason I am seeing this :
335 #log_call: running command btrfs subvolume delete /vservers/inri_sl1
336 #log_call: ERROR: cannot delete '/vservers/inri_sl1' - Device or resource busy
337 #log_call: Delete subvolume '/vservers/inri_sl1'
338 #log_call:end command (btrfs subvolume delete /vservers/inri_sl1) returned with code 1
340 # something must have an open handle to a file in there, but I can't find out what it is
341 # the following code aims at gathering data on what is going on in the system at this point in time
342 # note that some time later (typically when the sliver gets re-created) the same
343 # attempt at deleting the subvolume does work
344 # also lsof never shows anything relevant; this is painful..
346 if not os.path.exists(containerDir):
347 logger.log('sliver_lxc.destroy: %s cleanly destroyed.'%name)
350 #logger.log("-TMP-cwd %s : %s"%(name,os.getcwd()))
351 # also lsof never shows anything relevant; this is painful..
352 #logger.log("-TMP-lsof %s"%name)
354 #logger.log_call(command)
355 logger.log("-TMP-ls-l %s"%name)
356 command = ['ls', '-l', containerDir]
357 logger.log_call(command)
358 if os.path.exists(containerDir):
359 logger.log('sliver_lxc.destroy: ERROR could not cleanly destroy %s - giving up'%name)
361 if vsys_stopped: vsysStartService()