This repository has been archived by the owner on Sep 23, 2020. It is now read-only.
/
lvrt_common.py
402 lines (329 loc) · 14.9 KB
/
lvrt_common.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import fcntl
import os
import sys
import zope.interface
import libvirt
import workspacecontrol.api.modules
from workspacecontrol.api.exceptions import *
import workspacecontrol.main.wc_args as wc_args
from workspacecontrol.main import ACTIONS
import lvrt_adapter_xen3
import lvrt_adapter_kvm0
import lvrt_adapter_mock
import lvrt_model
class Platform:
zope.interface.implements(workspacecontrol.api.modules.IPlatform)
def __init__(self, params, common):
self.xen3 = False
self.kvm0 = False
self.create_flock = False
if params == None:
raise ProgrammingError("expecting params")
if common == None:
raise ProgrammingError("expecting common")
self.p = params
self.c = common
adapter_conf = self.p.get_conf_or_none("libvirt", "vmm")
if not adapter_conf:
raise InvalidConfig("Missing or invalid 'vmm' configuration in libvirt.conf")
if adapter_conf == "xen3":
self.adapter = lvrt_adapter_xen3.vmmadapter(params, common)
self.intakeadapter = lvrt_adapter_xen3.intakeadapter(params, common)
self.xen3 = True
# Because of a race between mount-alter.sh and Xen scripts for
# accessing loopback devices, we need to flock the same lock as
# mount-alter.sh
self.create_flock = True
elif adapter_conf == "kvm0":
self.adapter = lvrt_adapter_kvm0.vmmadapter(params, common)
self.intakeadapter = lvrt_adapter_kvm0.intakeadapter(params, common)
self.kvm0 = True
elif adapter_conf == "mock":
self.adapter = lvrt_adapter_mock.vmmadapter(params, common)
self.intakeadapter = lvrt_adapter_mock.intakeadapter(params, common)
self.xen3 = True
else:
raise InvalidConfig("Unknown 'vmm' configuration in libvirt conf: '%s'" % adapter_conf)
def validate(self):
self._validate_inputs_early()
self.c.log.debug("validating libvirt Platform module")
self.adapter.validate()
vmm = self._vmm()
domainIds = vmm.listDomainsID()
for did in domainIds:
avm = vmm.lookupByID(did)
self.c.log.debug("Found VM: id #%s, name '%s'" % (did, avm.name()))
def create(self, local_file_set, nic_set, kernel):
"""create launches a VM"""
model = self._fill_model(local_file_set, nic_set, kernel)
xml = model.toXML()
self.c.log.debug("XML being sent to libvirt:\n\n%s\n" % xml)
if self.c.dryrun:
self.c.log.debug("dryrun, not sending")
return
newvm = None
lockfile = None
try:
try:
if self.create_flock:
lockfilepath = self.c.resolve_var_dir("lock/loopback.lock")
if not os.path.exists(lockfilepath):
raise IncompatibleEnvironment("cannot find lock directory or lock file, make sure lock/loopback.lock exists")
lockfile = open(lockfilepath, "r")
fcntl.flock(lockfile.fileno(), fcntl.LOCK_EX)
newvm = self._vmm().createXML(xml, 0)
except libvirt.libvirtError,e:
shorterr = "Problem creating the VM: %s" % str(e)
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
finally:
if lockfile:
lockfile.close()
self.c.log.info("launched '%s'" % newvm.name())
def print_create_spec(self, local_file_set, nic_set, kernel):
"""If possible, print to stdout something that the platform adapter
produces for the underlying mechanism's creation call(s).
This is used for testing and debugging. This is not a requirement to
implement an IPlatform adapter, it could do nothing.
"""
model = self._fill_model(local_file_set, nic_set, kernel)
xml = model.toXML()
print xml
def destroy(self, running_vm):
"""destroy shuts a VM down instantly"""
name = running_vm.wchandle
vm = self._get_vm_by_handle(name)
if not vm:
err = "could not find VM with name '%s'" % name
raise UnexpectedError(err)
try:
vm.destroy()
except libvirt.libvirtError,e:
shorterr = "Problem destroying the '%s' VM: %s" % (name, str(e))
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def shutdown(self, running_vm):
"""shutdown shuts a VM down gracefully"""
name = running_vm.wchandle
vm = self._get_vm_by_handle(name)
if not vm:
err = "could not find VM with name '%s'" % name
raise UnexpectedError(err)
try:
vm.shutdown()
except libvirt.libvirtError,e:
shorterr = "Problem shutting down the '%s' VM: %s" % (name, str(e))
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def reboot(self, running_vm):
"""reboot reboots a running VM in place"""
name = running_vm.wchandle
vm = self._get_vm_by_handle(name)
if not vm:
err = "could not find VM with name '%s'" % name
raise UnexpectedError(err)
try:
vm.reboot(0)
except libvirt.libvirtError,e:
shorterr = "Problem rebooting the '%s' VM: %s" % (name, str(e))
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def pause(self, running_vm):
"""pause pauses a running VM in place"""
name = running_vm.wchandle
vm = self._get_vm_by_handle(name)
if not vm:
err = "could not find VM with name '%s'" % name
raise UnexpectedError(err)
try:
vm.suspend()
except libvirt.libvirtError,e:
shorterr = "Problem suspending (pausing) the '%s' VM: %s" % (name, str(e))
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def unpause(self, running_vm):
"""unpause unpauses a paused VM"""
name = running_vm.wchandle
vm = self._get_vm_by_handle(name)
if not vm:
err = "could not find VM with name '%s'" % name
raise UnexpectedError(err)
try:
vm.resume()
except libvirt.libvirtError,e:
shorterr = "Problem resuming (unpausing) the '%s' VM: %s" % (name, str(e))
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def info(self, handle):
"""info polls the current status of the VM
Return instance of RunningVM or None if the handle was not found.
"""
vm = self._get_vm_by_handle(handle)
if not vm:
return None
self.c.log.debug("found VM with name '%s'" % handle)
rvm_cls = self.c.get_class_by_keyword("RunningVM")
rvm = rvm_cls()
rvm.wchandle = handle
rvm.vmm_id = vm.ID()
rvm.vmm_uuid = vm.UUIDString()
rvm.xmldesc = vm.XMLDesc(0)
rvm.ostype = vm.OSType()
infolist = vm.info()
if not infolist:
raise UnexpectedError("Problem obtaining VM information from libvirt")
if len(infolist) != 5:
raise UnexpectedError("Unrecognized VM information from libvirt")
# from http://libvirt.org/python.html
# (0) state: one of the state values (virDomainState)
# (1) maxMemory: the maximum memory used by the domain
# (2) memory: the current amount of memory used by the domain
# (3) nbVirtCPU: the number of virtual CPU
# (4) cpuTime: the time used by the domain in nanoseconds
state = infolist[0] # see below
rvm.maxmem = infolist[1] # that's all for this
rvm.curmem = infolist[2] # that's all for this
rvm.numvcpus = infolist[3] # that's all for this
rvm.cputime = infolist[4] # that's all for this
# see http://libvirt.org/html/libvirt-libvirt.html#virDomainState
DOM_STATE_NOSTATE = 0 # no state
DOM_STATE_RUNNING = 1 # the domain is running
DOM_STATE_BLOCKED = 2 # the domain is blocked on resource
DOM_STATE_PAUSED = 3 # the domain is paused by user
DOM_STATE_SHUTDOWN = 4 # the domain is being shut down
DOM_STATE_SHUTOFF = 5 # the domain is shut off
DOM_STATE_CRASHED = 6 # the domain is crashed
if state not in range(0,7):
raise UnexpectedError("Unrecognized state information from libvirt: %s" % str(infolist[0]))
if state == DOM_STATE_NOSTATE:
# this is the case right after a graceful shutdown succeeds
self.c.log.debug("found VM with name '%s' but it has no state -- from the perspective 'above' this means it was not found at all." % handle)
return None
if state == DOM_STATE_RUNNING:
rvm.running = True
if state == DOM_STATE_BLOCKED:
rvm.blocked = True
if state == DOM_STATE_PAUSED:
rvm.paused = True
if state == DOM_STATE_SHUTDOWN:
rvm.shutting_down = True
if state == DOM_STATE_SHUTOFF:
rvm.shutoff = True
if state == DOM_STATE_CRASHED:
rvm.crashed = True
return rvm
# -----------------------------------------------------------------------------
def _vmm(self):
try:
return self.adapter.get_vmm_connection()
except libvirt.libvirtError,e:
shorterr = "Problem with connection to the VMM: %s" % str(e)
self.c.log.error(shorterr)
self.c.log.exception(e)
raise UnexpectedError(shorterr)
def _get_vm_by_handle(self, handle):
if not handle:
raise InvalidInput("No handle")
handle = handle.strip()
if not handle:
raise InvalidInput("No handle")
try:
return self._vmm().lookupByName(handle)
except:
shorterr = "Could not find domain with name '%s'" % handle
self.c.log.debug(shorterr)
return None
# -----------------------------------------------------------------------------
def _validate_inputs_early(self, dom=None):
"""validate the inputs that can be validated ahead of time without
any images, nics, etc.
dom -- if present, this method is overloaded to fill the model instance
"""
action = self.p.get_arg_or_none(wc_args.ACTION)
if not action:
# this situation is undefined, could be under unit test
# the actual user cmdline intake will require an action
return
if action in [ACTIONS.CREATE, ACTIONS.REMOVE, ACTIONS.INFO, ACTIONS.REBOOT, ACTIONS.PAUSE, ACTIONS.UNPAUSE, ACTIONS.PROPAGATE, ACTIONS.UNPROPAGATE, ACTIONS.PRINTXML]:
name = self.p.get_arg_or_none(wc_args.NAME)
if not name:
raise InvalidInput("The %s action requires a name" % action)
else:
raise InvalidInput("Unknown action: '%s'" % action)
if action == ACTIONS.CREATE or action == ACTIONS.PRINTXML:
self._validate_create_basics(dom)
def _validate_create_basics(self, dom):
name = self.p.get_arg_or_none(wc_args.NAME)
if not name:
raise InvalidInput("No name given for create")
if dom:
dom.name = name
self.c.log.debug("name for create: %s" % name)
# ---------------------------------------------------------------------
memory = self.p.get_arg_or_none(wc_args.MEMORY)
if not memory:
raise InvalidInput("No memory given for create")
try:
memory = int(memory)
except:
raise InvalidInput("memory given for create is not an integer: %s" % memory)
# convert MB -> kB
memory = memory * 1024
if dom:
dom.memory = memory
self.c.log.debug("memory for create: %d" % memory)
# ---------------------------------------------------------------------
vcpus = self.p.get_arg_or_none(wc_args.VCPUS)
if vcpus:
self.c.log.debug("vcpus given on cmdline: %s" % vcpus)
else:
vcpus = self.p.get_conf_or_none("vmcreation", "num_cpu_per_vm")
if vcpus:
self.c.log.debug("vcpu number retrieved from config file: %s" % vcpus)
else:
self.c.log.debug("no vcpu number given by argument or configuration, using default of 1")
if vcpus:
try:
vcpus = int(vcpus)
except:
raise InvalidInput("vcpus is not an integer: %s" % vcpus)
else:
vcpus = 1
if dom:
dom.vcpu = vcpus
# -----------------------------------------------------------------------------
def _fill_model(self, local_file_set, nic_set, kernel):
"""Construct a valid model object for creation, the valid mode object
can be converted directly to XML (via lvrt_xml routines) that can be
used as input to libvirt's virDomainCreateXML operation.
Steps to obtain a valid model object: a) validate the inputs, b) adjust
what will be sent based on available features/configurations, c) call
the appropriate lvrt_adapter* object to do driver-specific work on the
model object (there is particular syntax necessary depending on which
VMM is in use).
Return complete and valid instance of lvrt_model.Domain
"""
dom = lvrt_model.Domain()
dom.os = lvrt_model.OS()
dom.devices = lvrt_model.Devices()
self._validate_inputs_early(dom)
for lf in local_file_set.flist():
disk = lvrt_model.Disk()
disk.source = lf.path
disk.target = lf.mountpoint
disk.readonly = not lf.read_write
dom.devices.disks.append(disk)
for nic in nic_set.niclist():
interface = lvrt_model.Interface()
interface.source = nic.bridge
interface.mac = nic.mac
interface.target = nic.vifname
dom.devices.interfaces.append(interface)
self.intakeadapter.fill_model(dom, local_file_set, nic_set, kernel)
return dom