Refactor code. Put ebtables stuff at sliver creation time (start/stop) on
[nodemanager.git] / sliver_libvirt.py
1 #
2
3 """LibVirt slivers"""
4
5 import accounts
6 import logger
7 import subprocess
8 import os
9 import os.path
10 import libvirt
11 import sys
12 import shutil
13 import bwlimit
14
15 from string import Template
16
17 STATES = {
18     libvirt.VIR_DOMAIN_NOSTATE: 'no state',
19     libvirt.VIR_DOMAIN_RUNNING: 'running',
20     libvirt.VIR_DOMAIN_BLOCKED: 'blocked on resource',
21     libvirt.VIR_DOMAIN_PAUSED: 'paused by user',
22     libvirt.VIR_DOMAIN_SHUTDOWN: 'being shut down',
23     libvirt.VIR_DOMAIN_SHUTOFF: 'shut off',
24     libvirt.VIR_DOMAIN_CRASHED: 'crashed',
25 }
26
27 REF_IMG_BASE_DIR = '/vservers/.lvref'
28 CON_BASE_DIR     = '/vservers'
29
30 connections = dict()
31
32 # Helper methods
33
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 def debuginfo(dom):
42     ''' Helper method to get a "nice" output of the info struct for debug'''
43     [state, maxmem, mem, ncpu, cputime] = dom.info()
44     return '%s is %s, maxmem = %s, mem = %s, ncpu = %s, cputime = %s' % (dom.name(), STATES.get(state, state), maxmem, mem, ncpu, cputime)
45
46 # Common Libvirt code
47
48 class Sliver_Libvirt(accounts.Account):
49
50     def __init__(self, rec):
51         self.name = rec['name']
52         logger.verbose ('sliver_libvirt: %s init'%(self.name))
53          
54         # Assume the directory with the image and config files
55         # are in place
56         
57         self.keys = ''
58         self.rspec = {}
59         self.slice_id = rec['slice_id']
60         self.enabled = True
61         self.conn = getConnection(rec['type'])
62         self.xid = bwlimit.get_xid(self.name)
63         
64         try:
65             self.dom = self.conn.lookupByName(self.name)
66         except:
67             logger.verbose('sliver_libvirt: Domain %s does not exist UNEXPECTED: %s'%(self.name, sys.exc_info()[0]))
68
69
70     def start(self, delay=0):
71         ''' Just start the sliver '''
72         logger.verbose('sliver_libvirt: %s start'%(self.name))
73
74         # Check if it's running to avoid throwing an exception if the
75         # domain was already running, create actually means start
76         if not self.is_running():
77             self.dom.create()
78         else:
79             logger.verbose('sliver_libvirt: sliver %s already started'%(self.name))
80
81         # After the VM is started... we can play with the virtual interface
82         # Create the ebtables rule to mark the packets going out from the virtual
83         # interface to the actual device so the filter canmatch against the mark
84         bwlimit.ebtables("-A INPUT -i veth%d -j mark --set-mark %d" % \
85             (self.xid, self.xid))
86            
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 UNEXPECTED: %s'%(self.name, sys.exc_info()[0]))
99             print 'sliver_libvirt: Domain %s not running UNEXPECTED: %s'%(self.name, sys.exc_info()[0])
100         
101     def is_running(self):
102         ''' Return True if the domain is running '''
103         logger.verbose('sliver_libvirt: %s is_running'%self.name)
104         try:
105             [state, _, _, _, _] = self.dom.info()
106             if state == libvirt.VIR_DOMAIN_RUNNING:
107                 logger.verbose('sliver_libvirt: %s is RUNNING'%self.name)
108                 return True
109             else:
110                 info = debuginfo(self.dom)
111                 logger.verbose('sliver_libvirt: %s is NOT RUNNING...\n%s'%(self.name, info))
112                 return False
113         except:
114             logger.verbose('sliver_libvirt: UNEXPECTED ERROR in %s...\n%s'%(self.name, sys.exc_info[0]))
115             print 'sliver_libvirt: UNEXPECTED ERROR in %s...\n%s'%(self.name, sys.exc_info[0])
116
117     def configure(self, rec):
118
119         #sliver.[LXC/QEMU] tolower case
120         sliver_type = rec['type'].split('.')[1].lower() 
121
122         BASE_DIR = '/cgroup/libvirt/%s/%s/'%(sliver_type, self.name)
123
124         # Disk allocation
125         # No way through cgroups... figure out how to do that with user/dir quotas.
126         # There is no way to do quota per directory. Chown-ing would create
127         # problems as username namespaces are not yet implemented (and thus, host
128         # and containers share the same name ids
129
130         # Btrfs support quota per volumes
131
132         # It will depend on the FS selection
133         if rec.has_key('disk_max'):
134             disk_max = rec['disk_max']
135             if disk_max == 0:
136                 # unlimited 
137                 pass
138             else:
139                 # limit to certain number
140                 pass
141
142         # Memory allocation
143         if rec.has_key('memlock_hard'):
144             mem = rec['memlock_hard'] * 1024 # hard limit in bytes
145             with open(os.path.join(BASE_DIR, 'memory.limit_in_bytes'), 'w') as f:
146                 print >>f, mem
147         if rec.has_key('memlock_soft'):
148             mem = rec['memlock_soft'] * 1024 # soft limit in bytes
149             with open(os.path.join(BASE_DIR, 'memory.soft_limit_in_bytes'), 'w') as f:
150                 print >>f, mem
151
152         # CPU allocation
153         # Only cpu_shares until figure out how to provide limits and guarantees
154         # (RT_SCHED?)
155         if rec.has_key('cpu_share'): 
156             cpu_share = rec['cpu_share']
157             with open(os.path.join(BASE_DIR, 'cpu.shares'), 'w') as f:
158                 print >>f, cpu_share
159
160         # Call the upper configure method (ssh keys...)
161         accounts.Account.configure(self, rec)
162
163
164
165