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