no functional change, just log_exc to show hopefully more edible output
[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.log_exc("in sliver_libvirt.stop",name=self.name)
100
101     def is_running (self):
102         result=self._is_running()
103         logger.log("sliver_libvirt.is_running on %s returned %s"%(self.name,result))
104         return result
105
106     def _is_running(self):
107         ''' Return True if the domain is running '''
108         logger.verbose('sliver_libvirt: entering is_running on [%s:%s]'%(self.name,self.dom.ID()))
109         try:
110             state, _, _, _, _ = self.dom.info()
111             if state == libvirt.VIR_DOMAIN_RUNNING:
112                 logger.verbose('sliver_libvirt: %s is RUNNING'%self.name)
113                 return True
114             else:
115                 info = Sliver_Libvirt.debuginfo(self.dom)
116                 logger.verbose('sliver_libvirt: %s is ' \
117                                'NOT RUNNING...\n%s'%(self.name, info))
118                 return False
119         except:
120             logger.log("Re-fetching dom from name=%s"%self.name)
121             try:
122                 self.dom=self.conn.lookupByName(self.name)
123                 state, _, _, _, _ = self.dom.info()
124                 return state==libvirt.VIR_DOMAIN_RUNNING
125             except:
126                 logger.log_exc("in sliver_libvirt.is_running",name=self.name)
127                 return False
128
129     def configure(self, rec):
130
131         #sliver.[LXC/QEMU] tolower case
132         #sliver_type = rec['type'].split('.')[1].lower() 
133
134         #BASE_DIR = '/cgroup/libvirt/%s/%s/'%(sliver_type, self.name)
135
136         # Disk allocation
137         # No way through cgroups... figure out how to do that with user/dir quotas.
138         # There is no way to do quota per directory. Chown-ing would create
139         # problems as username namespaces are not yet implemented (and thus, host
140         # and containers share the same name ids
141
142         # Btrfs support quota per volumes
143
144         if rec.has_key("rspec") and rec["rspec"].has_key("tags"):
145             if cgroups.get_cgroup_path(self.name) == None:
146                 # If configure is called before start, then the cgroups won't exist
147                 # yet. NM will eventually re-run configure on the next iteration.
148                 # TODO: Add a post-start configure, and move this stuff there
149                 logger.log("Configure: postponing tag check on %s as cgroups are not yet populated" % self.name)
150             else:
151                 tags = rec["rspec"]["tags"]
152                 # It will depend on the FS selection
153                 if tags.has_key('disk_max'):
154                     disk_max = tags['disk_max']
155                     if disk_max == 0:
156                         # unlimited
157                         pass
158                     else:
159                         # limit to certain number
160                         pass
161
162                 # Memory allocation
163                 if tags.has_key('memlock_hard'):
164                     mem = str(int(tags['memlock_hard']) * 1024) # hard limit in bytes
165                     cgroups.write(self.name, 'memory.limit_in_bytes', mem, subsystem="memory")
166                 if tags.has_key('memlock_soft'):
167                     mem = str(int(tags['memlock_soft']) * 1024) # soft limit in bytes
168                     cgroups.write(self.name, 'memory.soft_limit_in_bytes', mem, subsystem="memory")
169
170                 # CPU allocation
171                 # Only cpu_shares until figure out how to provide limits and guarantees
172                 # (RT_SCHED?)
173                 if tags.has_key('cpu_share'):
174                     cpu_share = tags['cpu_share']
175                     cgroups.write(self.name, 'cpu.shares', cpu_share)
176
177         # Call the upper configure method (ssh keys...)
178         Account.configure(self, rec)
179
180     @staticmethod
181     def get_unique_vif():
182         return 'veth%s' % random.getrandbits(32)
183
184     # A placeholder until we get true VirtualInterface objects
185     @staticmethod
186     def get_interfaces_xml(rec):
187         xml = """
188     <interface type='network'>
189       <source network='default'/>
190       <target dev='%s'/>
191     </interface>
192 """ % (Sliver_Libvirt.get_unique_vif())
193         try:
194             tags = rec['rspec']['tags']
195             if 'interface' in tags:
196                 interfaces = eval(tags['interface'])
197                 if not isinstance(interfaces, (list, tuple)):
198                     # if interface is not a list, then make it into a singleton list
199                     interfaces = [interfaces]
200                 tag_xml = ""
201                 for interface in interfaces:
202                     if 'vlan' in interface:
203                         vlanxml = "<vlan><tag id='%s'/></vlan>" % interface['vlan']
204                     else:
205                         vlanxml = ""
206                     if 'bridge' in interface:
207                         tag_xml = tag_xml + """
208         <interface type='bridge'>
209           <source bridge='%s'/>
210           %s
211           <virtualport type='openvswitch'/>
212           <target dev='%s'/>
213         </interface>
214     """ % (interface['bridge'], vlanxml, Sliver_Libvirt.get_unique_vif())
215                     else:
216                         tag_xml = tag_xml + """
217         <interface type='network'>
218           <source network='default'/>
219           <target dev='%s'/>
220         </interface>
221     """ % (Sliver_Libvirt.get_unique_vif())
222
223                 xml = tag_xml
224                 logger.log('sliver_libvirty.py: interface XML is: %s' % xml)
225
226         except:
227             logger.log('sliver_libvirt.py: ERROR parsing "interface" tag for slice %s' % rec['name'])
228             logger.log('sliver_libvirt.py: tag value: %s' % tags['interface'])
229
230         return xml