10 from pwd import getpwnam
11 from string import Template
12 from plugins.vsys import removeSliverFromVsys
17 import plnode.bwlimit as bwlimit
18 from initscript import Initscript
19 from account import Account
20 from sliver_libvirt import Sliver_Libvirt
22 class Sliver_LXC(Sliver_Libvirt, Initscript):
23 """This class wraps LXC commands"""
25 SHELL = '/usr/sbin/vsh'
27 # Need to add a tag at myplc to actually use this account
30 REF_IMG_BASE_DIR = '/vservers/.lvref'
31 CON_BASE_DIR = '/vservers'
33 def __init__ (self, rec):
35 Sliver_Libvirt.__init__ (self,rec)
36 Initscript.__init__ (self,name)
38 def configure (self, rec):
39 Sliver_Libvirt.configure (self,rec)
41 # in case we update nodemanager..
42 self.install_and_enable_vinit()
43 # do the configure part from Initscript
44 Initscript.configure(self,rec)
46 def start(self, delay=0):
47 if 'enabled' in self.rspec and self.rspec['enabled'] <= 0:
48 logger.log('sliver_lxc: not starting %s, is not enabled'%self.name)
50 # the generic /etc/init.d/vinit script is permanently refreshed, and enabled
51 self.install_and_enable_vinit()
52 # expose .ssh for omf_friendly slivers
53 if 'tags' in self.rspec and 'omf_control' in self.rspec['tags']:
54 Account.mount_ssh_dir(self.name)
55 Sliver_Libvirt.start (self, delay)
56 # if a change has occured in the slice initscript, reflect this in /etc/init.d/vinit.slice
57 self.refresh_slice_vinit()
59 def rerun_slice_vinit (self):
60 """This is called whenever the initscript code changes"""
61 # xxx - todo - not sure exactly how to:
62 # (.) invoke something in the guest
63 # (.) which options of systemctl should be used to trigger a restart
64 # should not prevent the first run from going fine hopefully
65 logger.log("WARNING: sliver_lxc.rerun_slice_vinit not implemented yet")
68 def create(name, rec=None):
69 ''' Create dirs, copy fs image, lxc_create '''
70 logger.verbose ('sliver_lxc: %s create'%(name))
71 conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
73 # Get the type of image from vref myplc tags specified as:
79 tags = rec['rspec']['tags']
87 vref = "lxc-f18-x86_64"
88 logger.log("sliver_libvirt: %s: WARNING - no vref attached, using hard-wired default %s" % (name,vref))
90 refImgDir = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR, vref)
91 containerDir = os.path.join(Sliver_LXC.CON_BASE_DIR, name)
93 # check the template exists -- there's probably a better way..
94 if not os.path.isdir(refImgDir):
95 logger.log('sliver_lxc: %s: ERROR Could not create sliver - reference image %s not found' % (name,vref))
96 logger.log('sliver_lxc: %s: ERROR Expected reference image in %s'%(name,refImgDir))
99 # Snapshot the reference image fs (assume the reference image is in its own
101 command = ['btrfs', 'subvolume', 'snapshot', refImgDir, containerDir]
102 if not logger.log_call(command, timeout=15*60):
103 logger.log('sliver_lxc: ERROR Could not create BTRFS snapshot at', containerDir)
105 command = ['chmod', '755', containerDir]
106 logger.log_call(command, timeout=15*60)
108 # TODO: set quotas...
110 # Set hostname. A valid hostname cannot have '_'
111 #with open(os.path.join(containerDir, 'etc/hostname'), 'w') as f:
112 # print >>f, name.replace('_', '-')
114 # Add slices group if not already present
116 group = grp.getgrnam('slices')
118 command = ['/usr/sbin/groupadd', 'slices']
119 logger.log_call(command, timeout=15*60)
121 # Add unix account (TYPE is specified in the subclass)
122 command = ['/usr/sbin/useradd', '-g', 'slices', '-s', Sliver_LXC.SHELL, name, '-p', '*']
123 logger.log_call(command, timeout=15*60)
124 command = ['mkdir', '/home/%s/.ssh'%name]
125 logger.log_call(command, timeout=15*60)
127 # Create PK pair keys to connect from the host to the guest without
128 # password... maybe remove the need for authentication inside the
130 command = ['su', '-s', '/bin/bash', '-c', 'ssh-keygen -t rsa -N "" -f /home/%s/.ssh/id_rsa'%(name)]
131 logger.log_call(command, timeout=60)
133 command = ['chown', '-R', '%s.slices'%name, '/home/%s/.ssh'%name]
134 logger.log_call(command, timeout=30)
136 command = ['mkdir', '%s/root/.ssh'%containerDir]
137 logger.log_call(command, timeout=10)
139 command = ['cp', '/home/%s/.ssh/id_rsa.pub'%name, '%s/root/.ssh/authorized_keys'%containerDir]
140 logger.log_call(command, timeout=30)
142 logger.log("creating /etc/slicename file in %s" % os.path.join(containerDir,'etc/slicename'))
144 file(os.path.join(containerDir,'etc/slicename'), 'w').write(name)
146 logger.log_exc("exception while creating /etc/slicename")
149 file(os.path.join(containerDir,'etc/slicefamily'), 'w').write(vref)
151 logger.log_exc("exception while creating /etc/slicefamily")
155 uid = getpwnam(name).pw_uid
157 # keyerror will happen if user id was not created successfully
158 logger.log_exc("exception while getting user id")
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 etcgroup = os.path.join(containerDir, 'etc/group')
168 if os.path.exists(etcpasswd):
169 # create all accounts with gid=1001 - i.e. 'slices' like it is in the root context
171 logger.log("adding user %(name)s id %(uid)d gid %(slices_gid)d to %(etcpasswd)s" % (locals()))
173 file(etcpasswd,'a').write("%(name)s:x:%(uid)d:%(slices_gid)d::/home/%(name)s:/bin/bash\n" % locals())
175 logger.log_exc("exception while updating %s"%etcpasswd)
176 logger.log("adding group slices with gid %(slices_gid)d to %(etcgroup)s"%locals())
178 file(etcgroup,'a').write("slices:x:%(slices_gid)d\n"%locals())
180 logger.log_exc("exception while updating %s"%etcgroup)
181 sudoers = os.path.join(containerDir, 'etc/sudoers')
182 if os.path.exists(sudoers):
184 file(sudoers,'a').write("%s ALL=(ALL) NOPASSWD: ALL\n" % name)
186 logger.log_exc("exception while updating /etc/sudoers")
188 # customizations for the user environment - root or slice uid
189 # we save the whole business in /etc/planetlab.profile
190 # and source this file for both root and the slice uid's .profile
191 # prompt for slice owner, + LD_PRELOAD for transparently wrap bind
192 pl_profile=os.path.join(containerDir,"etc/planetlab.profile")
193 ld_preload_text="""# by default, we define this setting so that calls to bind(2),
194 # when invoked on 0.0.0.0, get transparently redirected to the public interface of this node
195 # see https://svn.planet-lab.org/wiki/LxcPortForwarding"""
196 usrmove_path_text="""# VM's before Features/UsrMove need /bin and /sbin in their PATH"""
197 usrmove_path_code="""
199 if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
200 if [ "$2" = "after" ] ; then
208 pathmunge /sbin after
211 with open(pl_profile,'w') as f:
212 f.write("export PS1='%s@\H \$ '\n"%(name))
213 f.write("%s\n"%ld_preload_text)
214 f.write("export LD_PRELOAD=/etc/planetlab/lib/bind_public.so\n")
215 f.write("%s\n"%usrmove_path_text)
216 f.write("%s\n"%usrmove_path_code)
218 # make sure this file is sourced from both root's and slice's .profile
219 enforced_line = "[ -f /etc/planetlab.profile ] && source /etc/planetlab.profile\n"
220 for path in [ 'root/.profile', 'home/%s/.profile'%name ]:
221 from_root=os.path.join(containerDir,path)
222 # if dir is not yet existing let's forget it for now
223 if not os.path.isdir(os.path.dirname(from_root)): continue
226 contents=file(from_root).readlines()
227 for content in contents:
228 if content==enforced_line: found=True
231 with open(from_root,"a") as user_profile:
232 user_profile.write(enforced_line)
233 # in case we create the slice's .profile when writing
234 if from_root.find("/home")>=0:
235 command=['chown','%s:slices'%name,from_root]
236 logger.log_call(command,timeout=5)
238 # Lookup for xid and create template after the user is created so we
239 # can get the correct xid based on the name of the slice
240 xid = bwlimit.get_xid(name)
242 # Template for libvirt sliver configuration
243 template_filename_sliceimage = os.path.join(Sliver_LXC.REF_IMG_BASE_DIR,'lxc_template.xml')
244 if os.path.isfile (template_filename_sliceimage):
245 logger.log("WARNING: using compat template %s"%template_filename_sliceimage)
246 template_filename=template_filename_sliceimage
248 logger.log("Cannot find XML template %s"%template_filename_sliceimage)
251 interfaces = Sliver_Libvirt.get_interfaces_xml(rec)
254 with open(template_filename) as f:
255 template = Template(f.read())
256 xml = template.substitute(name=name, xid=xid, interfaces=interfaces, arch=arch)
258 logger.log('Failed to parse or use XML template file %s'%template_filename)
261 # Lookup for the sliver before actually
262 # defining it, just in case it was already defined.
264 dom = conn.lookupByName(name)
266 dom = conn.defineXML(xml)
267 logger.verbose('lxc_create: %s -> %s'%(name, Sliver_Libvirt.dom_details(dom)))
272 # umount .ssh directory - only if mounted
273 Account.umount_ssh_dir(name)
274 logger.verbose ('sliver_lxc: %s destroy'%(name))
275 conn = Sliver_Libvirt.getConnection(Sliver_LXC.TYPE)
277 containerDir = Sliver_LXC.CON_BASE_DIR + '/%s'%(name)
280 # Destroy libvirt domain
281 dom = conn.lookupByName(name)
283 logger.verbose('sliver_lxc: Domain %s does not exist!' % name)
288 logger.verbose('sliver_lxc: Domain %s not running... continuing.' % name)
293 logger.verbose('sliver_lxc: Domain %s is not defined... continuing.' % name)
295 # Remove user after destroy domain to force logout
296 command = ['/usr/sbin/userdel', '-f', '-r', name]
297 logger.log_call(command, timeout=15*60)
299 # Slivers with vsys running will fail the subvolume delete.
300 # A more permanent solution may be to ensure that the vsys module
301 # is called before the sliver is destroyed.
302 removeSliverFromVsys (name)
304 # Remove rootfs of destroyed domain
305 command = ['btrfs', 'subvolume', 'delete', containerDir]
306 logger.log_call(command, timeout=60)
308 if os.path.exists(containerDir):
309 # oh no, it's still here...
310 logger.log("WARNING: failed to destroy container %s" % containerDir)
312 logger.verbose('sliver_libvirt: %s destroyed.'%name)