install for sytemd *or* init
[nodemanager.git] / sliver_libvirt.py
1 """LibVirt slivers"""
2
3 import sys
4 import os, os.path
5 import subprocess
6 import pprint
7 import random
8
9 import libvirt
10
11 from account import Account
12 import logger
13 import plnode.bwlimit as bwlimit
14 import cgroups
15
16 STATES = {
17     libvirt.VIR_DOMAIN_NOSTATE:  'no state',
18     libvirt.VIR_DOMAIN_RUNNING:  'running',
19     libvirt.VIR_DOMAIN_BLOCKED:  'blocked on resource',
20     libvirt.VIR_DOMAIN_PAUSED:   'paused by user',
21     libvirt.VIR_DOMAIN_SHUTDOWN: 'being shut down',
22     libvirt.VIR_DOMAIN_SHUTOFF:  'shut off',
23     libvirt.VIR_DOMAIN_CRASHED:  'crashed',
24 }
25
26 connections = dict()
27
28 # Common Libvirt code
29
30 class Sliver_Libvirt(Account):
31
32     # Helper methods
33
34     @staticmethod
35     def getConnection(sliver_type):
36         # TODO: error checking
37         # vtype is of the form sliver.[LXC/QEMU] we need to lower case to lxc/qemu
38         vtype = sliver_type.split('.')[1].lower()
39         uri = vtype + '://'
40         return connections.setdefault(uri, libvirt.open(uri))
41
42     @staticmethod
43     def debuginfo(dom):
44         ''' Helper method to get a "nice" output of the info struct for debug'''
45         [state, maxmem, mem, ncpu, cputime] = dom.info()
46         return '%s is %s, maxmem = %s, mem = %s, ncpu = %s, cputime = %s' % (dom.name(), STATES.get(state, state), maxmem, mem, ncpu, cputime)
47
48     def __init__(self, rec):
49         self.name = rec['name']
50         logger.verbose ('sliver_libvirt: %s init'%(self.name))
51
52         # Assume the directory with the image and config files
53         # are in place
54
55         self.keys = ''
56         self.rspec = {}
57         self.slice_id = rec['slice_id']
58         self.enabled = True
59         self.conn = Sliver_Libvirt.getConnection(rec['type'])
60         self.xid = bwlimit.get_xid(self.name)
61
62         dom = None
63         try:
64             dom = self.conn.lookupByName(self.name)
65         except:
66             logger.log('sliver_libvirt: Domain %s does not exist. ' \
67                        'Will try to create it again.' % (self.name))
68             self.__class__.create(rec['name'], rec)
69             dom = self.conn.lookupByName(self.name)
70         self.dom = dom
71
72     def start(self, delay=0):
73         ''' Just start the sliver '''
74         logger.verbose('sliver_libvirt: %s start'%(self.name))
75
76         # Check if it's running to avoid throwing an exception if the
77         # domain was already running, create actually means start
78         if not self.is_running():
79             self.dom.create()
80         else:
81             logger.verbose('sliver_libvirt: sliver %s already started'%(self.name))
82
83         # After the VM is started... we can play with the virtual interface
84         # Create the ebtables rule to mark the packets going out from the virtual
85         # interface to the actual device so the filter canmatch against the mark
86         bwlimit.ebtables("-A INPUT -i veth%d -j mark --set-mark %d" % \
87             (self.xid, self.xid))
88
89     def stop(self):
90         logger.verbose('sliver_libvirt: %s stop'%(self.name))
91
92         # Remove the ebtables rule before stopping 
93         bwlimit.ebtables("-D INPUT -i veth%d -j mark --set-mark %d" % \
94             (self.xid, self.xid))
95
96         try:
97             self.dom.destroy()
98         except:
99             logger.verbose('sliver_libvirt: Domain %s not running ' \
100                            'UNEXPECTED: %s'%(self.name, sys.exc_info()[1]))
101             print 'sliver_libvirt: Domain %s not running ' \
102                   'UNEXPECTED: %s'%(self.name, sys.exc_info()[1])
103
104     def is_running(self):
105         ''' Return True if the domain is running '''
106         logger.verbose('sliver_libvirt: %s is_running'%self.name)
107         try:
108             [state, _, _, _, _] = self.dom.info()
109             if state == libvirt.VIR_DOMAIN_RUNNING:
110                 logger.verbose('sliver_libvirt: %s is RUNNING'%self.name)
111                 return True
112             else:
113                 info = Sliver_Libvirt.debuginfo(self.dom)
114                 logger.verbose('sliver_libvirt: %s is ' \
115                                'NOT RUNNING...\n%s'%(self.name, info))
116                 return False
117         except:
118             logger.verbose('sliver_libvirt: UNEXPECTED ERROR in ' \
119                            '%s: %s'%(self.name, sys.exc_info()[1]))
120             print 'sliver_libvirt: UNEXPECTED ERROR in ' \
121                   '%s: %s'%(self.name, sys.exc_info()[1])
122             return False
123
124     def configure(self, rec):
125
126         #sliver.[LXC/QEMU] tolower case
127         #sliver_type = rec['type'].split('.')[1].lower() 
128
129         #BASE_DIR = '/cgroup/libvirt/%s/%s/'%(sliver_type, self.name)
130
131         # Disk allocation
132         # No way through cgroups... figure out how to do that with user/dir quotas.
133         # There is no way to do quota per directory. Chown-ing would create
134         # problems as username namespaces are not yet implemented (and thus, host
135         # and containers share the same name ids
136
137         # Btrfs support quota per volumes
138
139         if rec.has_key("rspec") and rec["rspec"].has_key("tags"):
140             if cgroups.get_cgroup_path(self.name) == None:
141                 # If configure is called before start, then the cgroups won't exist
142                 # yet. NM will eventually re-run configure on the next iteration.
143                 # TODO: Add a post-start configure, and move this stuff there
144                 logger.log("Configure: postponing tag check on %s as cgroups are not yet populated" % self.name)
145             else:
146                 tags = rec["rspec"]["tags"]
147                 # It will depend on the FS selection
148                 if tags.has_key('disk_max'):
149                     disk_max = tags['disk_max']
150                     if disk_max == 0:
151                         # unlimited
152                         pass
153                     else:
154                         # limit to certain number
155                         pass
156
157                 # Memory allocation
158                 if tags.has_key('memlock_hard'):
159                     mem = str(int(tags['memlock_hard']) * 1024) # hard limit in bytes
160                     cgroups.write(self.name, 'memory.limit_in_bytes', mem, subsystem="memory")
161                 if tags.has_key('memlock_soft'):
162                     mem = str(int(tags['memlock_soft']) * 1024) # soft limit in bytes
163                     cgroups.write(self.name, 'memory.soft_limit_in_bytes', mem, subsystem="memory")
164
165                 # CPU allocation
166                 # Only cpu_shares until figure out how to provide limits and guarantees
167                 # (RT_SCHED?)
168                 if tags.has_key('cpu_share'):
169                     cpu_share = tags['cpu_share']
170                     cgroups.write(self.name, 'cpu.shares', cpu_share)
171
172         # Call the upper configure method (ssh keys...)
173         Account.configure(self, rec)
174
175     @staticmethod
176     def get_unique_vif():
177         return 'veth%s' % random.getrandbits(32)
178
179     # A placeholder until we get true VirtualInterface objects
180     @staticmethod
181     def get_interfaces_xml(rec):
182         xml = """
183     <interface type='network'>
184       <source network='default'/>
185       <target dev='%s'/>
186     </interface>
187 """ % (Sliver_Libvirt.get_unique_vif())
188         try:
189             tags = rec['rspec']['tags']
190             if 'interface' in tags:
191                 interfaces = eval(tags['interface'])
192                 if not isinstance(interfaces, (list, tuple)):
193                     # if interface is not a list, then make it into a singleton list
194                     interfaces = [interfaces]
195                 tag_xml = ""
196                 for interface in interfaces:
197                     if 'vlan' in interface:
198                         vlanxml = "<vlan><tag id='%s'/></vlan>" % interface['vlan']
199                     else:
200                         vlanxml = ""
201                     if 'bridge' in interface:
202                         tag_xml = tag_xml + """
203         <interface type='bridge'>
204           <source bridge='%s'/>
205           %s
206           <virtualport type='openvswitch'/>
207           <target dev='%s'/>
208         </interface>
209     """ % (interface['bridge'], vlanxml, Sliver_Libvirt.get_unique_vif())
210                     else:
211                         tag_xml = tag_xml + """
212         <interface type='network'>
213           <source network='default'/>
214           <target dev='%s'/>
215         </interface>
216     """ % (Sliver_Libvirt.get_unique_vif())
217
218                 xml = tag_xml
219                 logger.log('sliver_libvirty.py: interface XML is: %s' % xml)
220
221         except:
222             logger.log('sliver_libvirt.py: ERROR parsing "interface" tag for slice %s' % rec['name'])
223             logger.log('sliver_libvirt.py: tag value: %s' % tags['interface'])
224
225         return xml