A small bug fix with somem formatting.
[nodemanager.git] / sliver_libvirt.py
1 """LibVirt slivers"""
2
3 import accounts
4 import logger
5 import subprocess
6 import os
7 import os.path
8 import libvirt
9 import sys
10 import shutil
11 import bwlimit
12 import cgroups
13 import pprint
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 connections = dict()
28
29 # Helper methods
30
31 def getConnection(sliver_type):
32     # TODO: error checking
33     # vtype is of the form sliver.[LXC/QEMU] we need to lower case to lxc/qemu
34     vtype = sliver_type.split('.')[1].lower()
35     uri = vtype + '://'
36     return connections.setdefault(uri, libvirt.open(uri))
37
38 def debuginfo(dom):
39     ''' Helper method to get a "nice" output of the info struct for debug'''
40     [state, maxmem, mem, ncpu, cputime] = dom.info()
41     return '%s is %s, maxmem = %s, mem = %s, ncpu = %s, cputime = %s' % (dom.name(), STATES.get(state, state), maxmem, mem, ncpu, cputime)
42
43 # Common Libvirt code
44
45 class Sliver_Libvirt(accounts.Account):
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 = getConnection(rec['type'])
59         self.xid = bwlimit.get_xid(self.name)
60
61         try:
62             self.dom = self.conn.lookupByName(self.name)
63         except:
64             logger.verbose('sliver_libvirt: Domain %s does not exist ' \
65                            'UNEXPECTED: %s'%(self.name, sys.exc_info()[1]))
66
67     def start(self, delay=0):
68         ''' Just start the sliver '''
69         logger.verbose('sliver_libvirt: %s start'%(self.name))
70
71         # Check if it's running to avoid throwing an exception if the
72         # domain was already running, create actually means start
73         if not self.is_running():
74             self.dom.create()
75         else:
76             logger.verbose('sliver_libvirt: sliver %s already started'%(self.name))
77
78         # After the VM is started... we can play with the virtual interface
79         # Create the ebtables rule to mark the packets going out from the virtual
80         # interface to the actual device so the filter canmatch against the mark
81         bwlimit.ebtables("-A INPUT -i veth%d -j mark --set-mark %d" % \
82             (self.xid, self.xid))
83
84     def stop(self):
85         logger.verbose('sliver_libvirt: %s stop'%(self.name))
86
87         # Remove the ebtables rule before stopping 
88         bwlimit.ebtables("-D INPUT -i veth%d -j mark --set-mark %d" % \
89             (self.xid, self.xid))
90
91         try:
92             self.dom.destroy()
93         except:
94             logger.verbose('sliver_libvirt: Domain %s not running ' \
95                            'UNEXPECTED: %s'%(self.name, sys.exc_info()[1]))
96             print 'sliver_libvirt: Domain %s not running ' \
97                   'UNEXPECTED: %s'%(self.name, sys.exc_info()[1])
98
99     def is_running(self):
100         ''' Return True if the domain is running '''
101         logger.verbose('sliver_libvirt: %s is_running'%self.name)
102         try:
103             [state, _, _, _, _] = self.dom.info()
104             if state == libvirt.VIR_DOMAIN_RUNNING:
105                 logger.verbose('sliver_libvirt: %s is RUNNING'%self.name)
106                 return True
107             else:
108                 info = debuginfo(self.dom)
109                 logger.verbose('sliver_libvirt: %s is ' \
110                                'NOT RUNNING...\n%s'%(self.name, info))
111                 return False
112         except:
113             logger.verbose('sliver_libvirt: UNEXPECTED ERROR in ' \
114                            '%s: %s'%(self.name, sys.exc_info()[1]))
115             print 'sliver_libvirt: UNEXPECTED ERROR in ' \
116                   '%s: %s'%(self.name, sys.exc_info()[1])
117             return False
118
119     def configure(self, rec):
120
121         #sliver.[LXC/QEMU] tolower case
122         #sliver_type = rec['type'].split('.')[1].lower() 
123
124         #BASE_DIR = '/cgroup/libvirt/%s/%s/'%(sliver_type, self.name)
125
126         # Disk allocation
127         # No way through cgroups... figure out how to do that with user/dir quotas.
128         # There is no way to do quota per directory. Chown-ing would create
129         # problems as username namespaces are not yet implemented (and thus, host
130         # and containers share the same name ids
131
132         # Btrfs support quota per volumes
133
134         # It will depend on the FS selection
135         if rec.has_key('disk_max'):
136             disk_max = rec['disk_max']
137             if disk_max == 0:
138                 # unlimited 
139                 pass
140             else:
141                 # limit to certain number
142                 pass
143
144         # Memory allocation
145         if rec.has_key('memlock_hard'):
146             mem = rec['memlock_hard'] * 1024 # hard limit in bytes
147             cgroups.write(self.name, 'memory.limit_in_bytes', mem)
148         if rec.has_key('memlock_soft'):
149             mem = rec['memlock_soft'] * 1024 # soft limit in bytes
150             cgroups.write(self.name, 'memory.soft_limit_in_bytes', 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             cgroups.write(self.name, 'cpu.shares', cpu_share)
158
159         # Call the upper configure method (ssh keys...)
160         accounts.Account.configure(self, rec)
161