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