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