Merge lp:~nuclearbob/utah/isoclass into lp:utah

Proposed by Max Brustkern
Status: Merged
Approved by: Javier Collado
Approved revision: 659
Merged at revision: 663
Proposed branch: lp:~nuclearbob/utah/isoclass
Merge into: lp:utah
Diff against target: 929 lines (+258/-257)
17 files modified
utah/config.py (+3/-1)
utah/exceptions.py (+3/-1)
utah/iso.py (+139/-3)
utah/provisioning/baremetal/cobbler.py (+27/-12)
utah/provisioning/baremetal/exceptions.py (+3/-1)
utah/provisioning/exceptions.py (+3/-2)
utah/provisioning/inventory/bootspeed.py (+0/-116)
utah/provisioning/inventory/exceptions.py (+3/-1)
utah/provisioning/inventory/inventory.py (+1/-1)
utah/provisioning/inventory/sqlite.py (+1/-1)
utah/provisioning/provisioning.py (+22/-89)
utah/provisioning/vm/exceptions.py (+3/-1)
utah/provisioning/vm/isotest.py (+0/-15)
utah/provisioning/vm/libvirtvm.py (+38/-8)
utah/provisioning/vm/vm.py (+5/-4)
utah/run.py (+3/-0)
utah/timeout.py (+4/-1)
To merge this branch: bzr merge lp:~nuclearbob/utah/isoclass
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Review via email: mp+121279@code.launchpad.net

Description of the change

I created an ISO class and moved many of the existing utah.iso calls into it. I haven't tried to convert the stuff in isotesting yet, but the stuff in provisioning should be moved over.

To post a comment you must log in.
Revision history for this message
Javier Collado (javier.collado) wrote :

Looked at the changes and they look nice. Also provisioned a VM from scratch using this branch and worked fine. Thanks.

My only comment is that please try to avoid unrelated changes in merge requests that fix a bug. I small refactoring is nice, but I think that removing a whole file and adding a lot of documentation strings should be part of a separate merge request.

review: Approve
Revision history for this message
Javier Collado (javier.collado) wrote :

Anyway, I've to say that changes were properly separated in different commits, so it wasn't so difficult to review them (maybe I was a little bit picky in my comment above).

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I meant to do those separately, I must have just based this branch on my working copy or something instead of the branch in launchpad. I'll keep better track of that in the future to keep them separated.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'utah/config.py'
2--- utah/config.py 2012-08-13 20:37:09 +0000
3+++ utah/config.py 2012-08-24 20:25:19 +0000
4@@ -75,8 +75,10 @@
5 CONFIG = {}
6 CONFIG.update(DEFAULTS)
7
8+"""
9+Process config files.
10+"""
11 files = ['/etc/utah/config', '~/.utah/config', os.getenv('UTAH_CONFIG_FILE')]
12-
13 for conffile in files:
14 if conffile is not None:
15 myfile = os.path.expanduser(conffile)
16
17=== modified file 'utah/exceptions.py'
18--- utah/exceptions.py 2012-07-19 13:25:30 +0000
19+++ utah/exceptions.py 2012-08-24 20:25:19 +0000
20@@ -1,4 +1,6 @@
21-#!/usr/bin/python
22+"""
23+Provide exceptions for the rest of the tool.
24+"""
25
26
27 class UTAHException(Exception):
28
29=== modified file 'utah/iso.py'
30--- utah/iso.py 2012-08-13 20:37:09 +0000
31+++ utah/iso.py 2012-08-24 20:25:19 +0000
32@@ -1,12 +1,17 @@
33 """
34 All commands use bsdtar and accept a logmethod to use for logging.
35-TODO: Make an iso class that can return things like arch, series, type, etc.
36-TODO: Add the existing functions to the iso class.
37-TODO: Convert the code to use the class instead of the existing functions.
38 """
39+#TODO: Make an iso class that can return things like arch, series, type, etc.
40+#TODO: Add the existing functions to the iso class.
41+#TODO: Convert the code to use the class instead of the existing functions.
42+import logging
43+import logging.handlers
44 import os
45 import subprocess
46+import sys
47+import urllib
48
49+from utah import config
50 from utah.exceptions import UTAHException
51
52
53@@ -104,3 +109,134 @@
54 """
55 cmd = getrealfile(image, filename, logmethod=logmethod)
56 return subprocess.Popen(cmd, stdout=subprocess.PIPE, **kw)
57+
58+
59+class ISO(object):
60+ """
61+ Provide a simplified method of interfacing with images.
62+ """
63+ def __init__(self, image, dlpercentincrement=1, logger=None):
64+ self.dlpercentincrement = dlpercentincrement
65+ if logger is None:
66+ self._loggersetup()
67+ else:
68+ self.logger = logger
69+ if image.startswith('~'):
70+ path = os.path.expanduser(image)
71+ self.logger.debug('Rewriting ~-based path: ' + image + ' to: ' + path)
72+ else:
73+ path = image
74+
75+ self.percent = 0
76+ self.logger.info('Preparing image: ' + path)
77+ self.image = urllib.urlretrieve(path, reporthook=self.dldisplay)[0]
78+ self.logger.debug(path + ' is locally available as ' + self.image)
79+ self.installtype = self.getinstalltype()
80+ if self.installtype == 'mini':
81+ self.logger.debug('Using mini image')
82+ self.infofile = '.disk/mini-info'
83+ else:
84+ self.logger.debug('Using normal image')
85+ self.infofile = './.disk/info'
86+ # Info file looks like this:
87+ # Ubuntu 12.04 LTS "Precise Pangolin" - Release i386 (20120423)
88+ # or this:
89+ # Ubuntu 12.10 "Quantal Quetzal" - Alpha amd64 (20120820)
90+ # i.e. Ubuntu Version (LTS) "Series Codename" - Alpha/Beta/Release
91+ # arch (buildnumber)
92+ self.arch = self.getarch()
93+ self.series = self.getseries()
94+
95+ def _loggersetup(self):
96+ """
97+ Initialize the logging for the image.
98+ """
99+ self.logger = logging.getLogger('utah.iso')
100+ self.logger.propagate = False
101+ self.logger.setLevel(logging.INFO)
102+ for handler in list(self.logger.handlers):
103+ self.logger.removeHandler(handler)
104+ del handler
105+ self.consolehandler = logging.StreamHandler(stream=sys.stderr)
106+ console_formatter = logging.Formatter('%(levelname)s: %(message)s')
107+ self.consolehandler.setFormatter(console_formatter)
108+ self.consolehandler.setLevel(config.consoleloglevel)
109+ self.logger.addHandler(self.consolehandler)
110+ self.filehandler = logging.handlers.WatchedFileHandler(config.logfile)
111+ file_formatter = logging.Formatter('%(asctime)s iso '
112+ + ' %(levelname)s: %(message)s')
113+ self.filehandler.setFormatter(file_formatter)
114+ self.filehandler.setLevel(config.fileloglevel)
115+ self.logger.addHandler(self.filehandler)
116+ if config.debuglog is not None:
117+ self.logger.setLevel(logging.DEBUG)
118+ self.debughandler = (
119+ logging.handlers.WatchedFileHandler(config.debuglog))
120+ self.debughandler.setFormatter(file_formatter)
121+ self.debughandler.setLevel(logging.DEBUG)
122+ self.logger.addHandler(self.debughandler)
123+
124+ def getinstalltype(self):
125+ """
126+ Inspect the image's files to get the image type.
127+ If .disk/mini-info exists, it's mini.
128+ If the casper directory exists, it's desktop.
129+ If ubuntu-server.seed exists in the preseeds directory, it's server.
130+ """
131+ self.logger.info('Getting image type of ' + self.image)
132+ files = self.listfiles(returnlist=True)
133+ installtype = 'alternate'
134+ if '.disk/mini-info' in files:
135+ installtype = 'mini'
136+ elif './casper' in files:
137+ installtype = 'desktop'
138+ elif './preseed/ubuntu-server.seed' in files:
139+ installtype = 'server'
140+ self.logger.info('Image type is: ' + installtype)
141+ return installtype
142+
143+ def getarch(self):
144+ """
145+ Unpack the image's info file to get the arch.
146+ """
147+ stdout = self.dump(self.infofile).communicate()[0]
148+ arch = stdout.split()[-2]
149+ self.logger.info('Arch is: ' + arch)
150+ return arch
151+
152+ def getseries(self):
153+ """
154+ Unpack the image's info file to get the series.
155+ """
156+ stdout = self.dump(self.infofile).communicate()[0]
157+ for word in stdout.split():
158+ if word.startswith('"'):
159+ series = word.strip('"').lower()
160+ break
161+ self.logger.info('Series is ' + series)
162+ return series
163+
164+ def dldisplay(self, blocks, size, total):
165+ read = blocks * size
166+ percent = 100 * read / total
167+ if percent >= self.percent:
168+ self.logger.info('File ' + str(percent) + '% downloaded')
169+ self.percent += self.dlpercentincrement
170+ self.logger.debug("%s read, %s%% of %s total" % (read, percent, total))
171+
172+ def listfiles(self, returnlist=False):
173+ return listfiles(self.image, self.logger.debug, returnlist=returnlist)
174+
175+ def getrealfile(self, filename, outdir=None):
176+ return getrealfile(self.image, filename, outdir=outdir, logmethod=self.logger.debug)
177+
178+ def extract(self, filename, outdir=None, outfile=None, **kw):
179+ return extract(self.image, filename, outdir, outfile, logmethod=self.logger.debug, **kw)
180+
181+ def dump(self, filename, **kw):
182+ return dump(self.image, filename, logmethod=self.logger.debug, **kw)
183+
184+ def extractall(self):
185+ for myfile in self.listfiles(returnlist=True):
186+ self.extract(myfile)
187+
188
189=== modified file 'utah/provisioning/baremetal/cobbler.py'
190--- utah/provisioning/baremetal/cobbler.py 2012-08-20 10:20:44 +0000
191+++ utah/provisioning/baremetal/cobbler.py 2012-08-24 20:25:19 +0000
192@@ -1,4 +1,6 @@
193-#!/usr/bin/python
194+"""
195+Support bare metal provisioning through cobbler.
196+"""
197
198 import os
199 import shutil
200@@ -12,23 +14,22 @@
201 Machine,
202 SSHMixin,
203 )
204-from utah.provisioning.exceptions import UTAHProvisioningException
205+from utah.provisioning.baremetal.exceptions import UTAHBMProvisioningException
206 from utah.retry import retry
207
208
209 class CobblerMachine(CustomInstallMixin, SSHMixin, Machine):
210 """
211 Provide a class to provision a machine via cobbler.
212-
213 """
214 # TODO: may need to fix DHCP/hostname issues, may just be magners
215 def __init__(self, boot=None, cargs=None, inventory=None,
216 name=None, preseed=None, *args, **kw):
217 if name is None:
218- raise UTAHProvisioningException(
219+ raise UTAHBMProvisioningException(
220 'Machine name reqired for cobbler machine')
221 if cargs is None:
222- raise UTAHProvisioningException(
223+ raise UTAHBMProvisioningException(
224 'No cobbler arguments given for machine creation')
225 else:
226 self.cargs = cargs
227@@ -38,7 +39,7 @@
228 super(CobblerMachine, self).__init__(*args, name=name,
229 preseed=preseed, **kw)
230 if self.image is None:
231- raise UTAHProvisioningException(
232+ raise UTAHBMProvisioningException(
233 'Image file required for cobbler installation')
234 self._custominit(arch=self.arch, boot=boot,
235 installtype=self.installtype, series=self.series)
236@@ -50,7 +51,7 @@
237 'ubuntu-installer', self.arch,
238 'initrd.gz')
239 else:
240- raise UTAHProvisioningException(
241+ raise UTAHBMProvisioningException(
242 'Only alternate and server images currently supported '
243 'for cobbler provisioning')
244 if self.arch == 'amd64':
245@@ -63,16 +64,17 @@
246
247 def _provision(self, checktimeout=config.checktimeout,
248 installtimeout=config.installtimeout):
249+ """
250+ Install a machine.
251+ """
252+ # TODO: support for reusing existing machines
253 self.tmpdir = tempfile.mkdtemp(prefix='/tmp/' + self.name + '_')
254 os.chdir(self.tmpdir)
255
256 # TODO: try to remove this step, mount the iso and import that
257 # with a specified kernel/preseed/initrd from the tmpdir
258 self.logger.info('Extracting image to ' + self.tmpdir)
259- files = utah.iso.listfiles(self.image, logmethod=self.logger.debug,
260- returnlist=True)
261- for myfile in files:
262- utah.iso.extract(self.image, myfile, logmethod=self.logger.debug)
263+ self.image.extractall()
264
265 self._preparekernel()
266 cinitrd = os.path.join(self.tmpdir, self.cinitrd)
267@@ -149,7 +151,7 @@
268 .format(uuid=self.uuid))
269 if self.run(uuid_check_command)[0] != 0:
270 self.provisioned = False
271- raise UTAHProvisioningException(
272+ raise UTAHBMProvisioningException(
273 'Installed UUID differs from CobblerMachine UUID; '
274 'installation probably failed. '
275 'Try restarting cobbler or running cobbler sync')
276@@ -157,6 +159,11 @@
277 return True
278
279 def destroy(self, *args, **kw):
280+ """
281+ Destroy the machine, and call super to release it in the inventory.
282+ """
283+ # TODO: Remove the machine from cobbler
284+ # TODO: Consider actually wrecking the install
285 super(CobblerMachine, self).destroy(*args, **kw)
286 self.__del__()
287 del self
288@@ -185,6 +192,9 @@
289 self.active = False
290
291 def restart(self):
292+ """
293+ Stop the machine, wait powertimeout, then start it/
294+ """
295 self.logger.info('Restarting system')
296 self.stop()
297 self.logger.debug('Waiting ' + str(config.powertimeout) + ' seconds')
298@@ -192,6 +202,10 @@
299 self._start()
300
301 def __del__(self):
302+ """
303+ If the machine has an inventory, tell the inventory to release the
304+ machine when the object is deleted.
305+ """
306 if self.inventory is not None:
307 self.inventory.release(machine=self)
308
309@@ -199,6 +213,7 @@
310 """
311 Start the machine if needed, and check for SSH login.
312 """
313+ # TODO: Consider moving this to SSHMixin
314 self.logger.debug('Checking if machine is active')
315 self.provisioncheck()
316 if not self.active:
317
318=== modified file 'utah/provisioning/baremetal/exceptions.py'
319--- utah/provisioning/baremetal/exceptions.py 2012-07-19 09:36:34 +0000
320+++ utah/provisioning/baremetal/exceptions.py 2012-08-24 20:25:19 +0000
321@@ -1,4 +1,6 @@
322-#!/usr/bin/python
323+"""
324+Provide exceptions specific to bare metal provisioning.
325+"""
326
327 from utah.provisioning.exceptions import UTAHProvisioningException
328
329
330=== modified file 'utah/provisioning/exceptions.py'
331--- utah/provisioning/exceptions.py 2012-07-24 11:08:49 +0000
332+++ utah/provisioning/exceptions.py 2012-08-24 20:25:19 +0000
333@@ -1,5 +1,6 @@
334-#!/usr/bin/python
335-
336+"""
337+Provide exceptions specific to provisioning.
338+"""
339 from utah.exceptions import UTAHException
340
341
342
343=== removed file 'utah/provisioning/inventory/bootspeed.py'
344--- utah/provisioning/inventory/bootspeed.py 2012-08-20 10:20:44 +0000
345+++ utah/provisioning/inventory/bootspeed.py 1970-01-01 00:00:00 +0000
346@@ -1,116 +0,0 @@
347-#!/usr/bin/python
348-
349-import os
350-from utah.provisioning.inventory.exceptions import \
351- UTAHProvisioningInventoryException
352-from utah.provisioning.inventory.sqlite import SQLiteInventory
353-from utah.provisioning.baremetal.cobbler import CobblerMachine
354-
355-
356-class BootspeedInventory(SQLiteInventory):
357- """
358- Keep an inventory of manually entered machines for bootspeed testing.
359- All columns other than machineid, name, and state are assumed to be
360- arguments for cobbler system creation.
361- """
362- def __init__(self, db='~/.utah-bootspeed-inventory',
363- lockfile='~/.utah-bootspeed-lock', *args, **kw):
364- db = os.path.expanduser(db)
365- lockfile = os.path.expanduser(lockfile)
366- if not os.path.isfile(db):
367- raise UTAHProvisioningInventoryException(
368- 'No machine database found at ' + db)
369- super(BootspeedInventory, self).__init__(*args, db=db,
370- lockfile=lockfile, **kw)
371- machines_count = (self.connection
372- .execute('SELECT COUNT(*) FROM machines')
373- .fetchall()[0][0])
374- if machines_count == 0:
375- raise UTAHProvisioningInventoryException('No machines in database')
376- self.machines = []
377-
378- def request(self, name=None, *args, **kw):
379- query = 'SELECT * FROM machines'
380- queryvars = []
381- if name is not None:
382- query += ' WHERE name=?'
383- queryvars.append(name)
384- result = self.connection.execute(query, queryvars).fetchall()
385- if result is None:
386- raise UTAHProvisioningInventoryException(
387- 'No machines meet criteria')
388- else:
389- for machineinfo in result:
390- cargs = dict(machineinfo)
391- machineid = cargs.pop('machineid')
392- name = cargs.pop('name')
393- state = cargs.pop('state')
394- if state == 'available':
395- update = self.connection.execute(
396- "UPDATE machines SET state='provisioned' "
397- "WHERE machineid=? AND state='available'",
398- [machineid]).rowcount
399- if update == 1:
400- machine = CobblerMachine(*args, cargs=cargs,
401- inventory=self, name=name, **kw)
402- self.machines.append(machine)
403- return machine
404- elif update == 0:
405- raise UTAHProvisioningInventoryException(
406- 'Machine was requested by another process '
407- 'before we could request it')
408- elif update > 1:
409- raise UTAHProvisioningInventoryException(
410- 'Multiple machines exist '
411- 'matching those criteria; '
412- 'database ' + self.db + ' may be corrupt')
413- else:
414- raise UTAHProvisioningInventoryException(
415- 'Negative rowcount returned '
416- 'when attempting to request machine')
417- raise UTAHProvisioningInventoryException(
418- 'All machines meeting criteria are currently unavailable')
419-
420- def release(self, machine=None, name=None):
421- if machine is not None:
422- name = machine.name
423- if name is None:
424- raise UTAHProvisioningInventoryException(
425- 'name required to release a machine')
426- query = "UPDATE machines SET state='available' WHERE name=?"
427- queryvars = [name]
428- update = self.connection.execute(query, queryvars).rowcount
429- if update == 1:
430- if machine is not None:
431- if machine in self.machines:
432- self.machines.remove(machine)
433- elif update == 0:
434- raise UTAHProvisioningInventoryException(
435- 'SERIOUS ERROR: Another process released this machine '
436- 'before we could, which means two processes provisioned '
437- 'the same machine simultaneously')
438- elif update > 1:
439- raise UTAHProvisioningInventoryException(
440- 'Multiple machines exist matching those criteria; '
441- 'database ' + self.db + ' may be corrupt')
442- else:
443- raise UTAHProvisioningInventoryException(
444- 'Negative rowcount returned '
445- 'when attempting to request machine')
446-
447-# Here is how I currently create the database:
448-# CREATE TABLE machines (machineid INTEGER PRIMARY KEY,
449-# name TEXT NOT NULL UNIQUE,
450-# state TEXT default 'available',
451-# [mac-address] TEXT NOT NULL UNIQUE,
452-# [power-address] TEXT DEFAULT '10.97.0.13',
453-# [power-id] TEXT,
454-# [power-user] TEXT DEFAULT 'ubuntu',
455-# [power-pass] TEXT DEFAULT 'ubuntu',
456-# [power-type] DEFAULT 'sentryswitch_cdu');
457-
458-# Here is how I currently populate the database:
459-# INSERT INTO machines (name, [mac-address], [power-id])
460-# VALUES ('acer-veriton-01-Pete', 'd0:27:88:9f:73:ce', 'Veriton_1');
461-# INSERT INTO machines (name, [mac-address], [power-id])
462-# VALUES ('acer-veriton-02-Pete', 'd0:27:88:9b:84:5b', 'Veriton_2');
463
464=== modified file 'utah/provisioning/inventory/exceptions.py'
465--- utah/provisioning/inventory/exceptions.py 2012-07-19 09:36:34 +0000
466+++ utah/provisioning/inventory/exceptions.py 2012-08-24 20:25:19 +0000
467@@ -1,4 +1,6 @@
468-#!/usr/bin/python
469+"""
470+Provide excpetions specific to inventory issues.
471+"""
472
473 from utah.provisioning.exceptions import UTAHProvisioningException
474
475
476=== modified file 'utah/provisioning/inventory/inventory.py'
477--- utah/provisioning/inventory/inventory.py 2012-07-24 14:07:41 +0000
478+++ utah/provisioning/inventory/inventory.py 2012-08-24 20:25:19 +0000
479@@ -63,7 +63,7 @@
480
481 def write(self):
482 """
483- Force a commit all current inventory data in memory to internal or
484+ Force a commit of all current inventory data in memory to internal or
485 external storage.
486 """
487 return False
488
489=== modified file 'utah/provisioning/inventory/sqlite.py'
490--- utah/provisioning/inventory/sqlite.py 2012-08-20 10:20:44 +0000
491+++ utah/provisioning/inventory/sqlite.py 2012-08-24 20:25:19 +0000
492@@ -177,7 +177,7 @@
493 # [power-id] TEXT,
494 # [power-user] TEXT DEFAULT 'ubuntu',
495 # [power-pass] TEXT DEFAULT 'ubuntu',
496- # [power-type] DEFAULT 'sentryswitch_cdu');
497+ # [power-type] TEXT DEFAULT 'sentryswitch_cdu');
498
499 # Here is how I currently populate the database:
500 # INSERT INTO machines (name, [mac-address], [power-id])
501
502=== modified file 'utah/provisioning/provisioning.py'
503--- utah/provisioning/provisioning.py 2012-08-20 19:56:21 +0000
504+++ utah/provisioning/provisioning.py 2012-08-24 20:25:19 +0000
505@@ -18,10 +18,11 @@
506 import re
507 import apt.cache
508
509+from utah.iso import ISO
510 from utah.provisioning.exceptions import UTAHProvisioningException
511 from utah.retry import retry
512 import utah.timeout
513-from utah import config, iso
514+from utah import config
515
516
517 class Machine(object):
518@@ -94,7 +95,14 @@
519 self.active = False
520 self._loggersetup()
521
522- for item in ['preseed', 'xml', 'kernel', 'initrd', 'image']:
523+ if image is None:
524+ self.image = None
525+ else:
526+ self.image = ISO(image,
527+ dlpercentincrement=self.dlpercentincrement,
528+ logger=self.logger)
529+
530+ for item in ['preseed', 'xml', 'kernel', 'initrd']:
531 # Ensure every file/url type argument is available locally
532 arg = locals()[item]
533 if arg is None:
534@@ -634,87 +642,12 @@
535 rewriting them to our liking, and repacking them.
536 """
537 # TODO: setup variable(s) to suppress adding and rewriting
538-
539- def _getinstalltypefromimage(self, image=None):
540- """
541- Inspect the image's files to get the image type.
542- If .disk/mini-info exists, it's mini.
543- If the casper directory exists, it's desktop.
544- If ubuntu-server.seed exists in the preseeds directory, it's server.
545- """
546- # TODO: move this into the iso class after it exists
547- if image is None:
548- image = self.image
549- self.logger.info('Getting image type of ' + image)
550- files = iso.listfiles(image,
551- logmethod=self.logger.debug, returnlist=True)
552- installtype = 'alternate'
553- if '.disk/mini-info' in files:
554- installtype = 'mini'
555- elif './casper' in files:
556- installtype = 'desktop'
557- elif './preseed/ubuntu-server.seed' in files:
558- installtype = 'server'
559- self.logger.info('Image type is: ' + installtype)
560- return installtype
561-
562- def _getarchfromimage(self, image=None, installtype=None):
563- """
564- Unpack the image's info file to get the arch.
565- Mini images use .disk/mini-info, everything else uses .disk/info.
566- """
567- # TODO: document the format of the info file
568- # TODO: move this into the iso class after it exists
569- if image is None:
570- image = self.image
571- if installtype is None:
572- installtype = self.installtype
573- self.logger.info('Getting arch of ' + image)
574- if installtype == 'mini':
575- self.logger.debug('Checking mini image')
576- infofile = '.disk/mini-info'
577- else:
578- self.logger.debug('Checking normal image')
579- infofile = './.disk/info'
580- stdout = (iso.dump(image, infofile, logmethod=logging.debug)
581- .communicate()[0])
582- arch = stdout.split()[-2]
583- self.logger.info('Arch is: ' + arch)
584- return arch
585-
586- def _getseriesfromimage(self, image=None, installtype=None):
587- """
588- Unpack the image's info file to get the series.
589- Mini images use .disk/mini-info, everything else uses .disk/info.
590- """
591- # TODO: document the format of the info file
592- # TODO: move this into the iso class after it exists
593- if image is None:
594- image = self.image
595- if installtype is None:
596- installtype = self.installtype
597- self.logger.info('Getting series of ' + image)
598- if installtype == 'mini':
599- self.logger.debug('Checking mini image')
600- infofile = '.disk/mini-info'
601- else:
602- self.logger.debug('Checking normal image')
603- infofile = './.disk/info'
604- stdout = (iso.dump(image, infofile, logmethod=logging.debug)
605- .communicate()[0])
606- for word in stdout.split():
607- if word.startswith('"'):
608- series = word.strip('"').lower()
609- break
610- self.logger.info('Series is ' + series)
611- return series
612-
613 def _preparekernel(self, kernel=None, tmpdir=None):
614 """
615 If we haven't been given a kernel file, unpack one from the image.
616 """
617 # TODO: document this better
618- # TODO: consider moving this into the iso class when it exists
619+ # TODO: consider moving this into the ISO class when it exists
620 self.logger.info('Preparing kernel')
621 if kernel is None:
622 kernel = self.kernel
623@@ -730,8 +663,7 @@
624 self.logger.debug('Desktop image detected')
625 kernelpath = './casper/vmlinuz'
626 kernel = os.path.join(tmpdir, 'kernel')
627- iso.extract(self.image, kernelpath,
628- outfile=kernel, logmethod=logging.debug)
629+ self.image.extract(kernelpath, outfile=kernel)
630 else:
631 self.logger.info('Using local kernel: ' + kernel)
632 return kernel
633@@ -741,7 +673,7 @@
634 If we haven't been given an initrd file, unpack one from the image.
635 """
636 # TODO: document this better
637- # TODO: consider moving this into the iso class when it exists
638+ # TODO: consider moving this into the ISO class when it exists
639 self.logger.info('Preparing initrd')
640 if initrd is None:
641 initrd = self.initrd
642@@ -757,8 +689,7 @@
643 self.logger.debug('Desktop image detected')
644 initrdpath = './casper/initrd.lz'
645 initrd = os.path.join(tmpdir, os.path.basename(initrdpath))
646- iso.extract(self.image, initrdpath,
647- outfile=initrd, logmethod=logging.debug)
648+ self.image.extract(initrdpath, outfile=initrd)
649 else:
650 self.logger.info('Using local initrd: ' + initrd)
651 return initrd
652@@ -874,7 +805,7 @@
653 .format(pkgname))
654 line = line.replace('string ',
655 'string {} '.format(pkgname))
656- line = line.rstrip()
657+ line = line.rstrip() + "\n"
658 if 'netcfg/get_hostname' in line:
659 self.logger.info('Rewriting hostname to ' + self.name)
660 line = 'd-i netcfg/get_hostname string ' + self.name + "\n"
661@@ -1018,13 +949,15 @@
662
663 def _custominit(self, arch=None, boot=None, installtype=None, series=None):
664 """
665- TODO: Make this a proper __init__ method
666- by adding super call to Machine.
667+ Setup installtype, arch, series and commandline.
668+ Everything but commandline setup may be replaced by ISO class later.
669 """
670+ #TODO: Make this a proper __init__ method
671+ #by adding super call to Machine.
672 if installtype is None:
673- self.installtype = self._getinstalltypefromimage()
674+ self.installtype = self.image.installtype
675 if arch is None:
676- self.arch = self._getarchfromimage()
677+ self.arch = self.image.arch
678 if series is None:
679- self.series = self._getseriesfromimage()
680+ self.series = self.image.series
681 self._cmdlinesetup(boot=boot)
682
683=== modified file 'utah/provisioning/vm/exceptions.py'
684--- utah/provisioning/vm/exceptions.py 2012-07-19 09:36:34 +0000
685+++ utah/provisioning/vm/exceptions.py 2012-08-24 20:25:19 +0000
686@@ -1,4 +1,6 @@
687-#!/usr/bin/python
688+"""
689+Provide excpetions specific to virtual machines.
690+"""
691
692 from utah.provisioning.exceptions import UTAHProvisioningException
693
694
695=== removed file 'utah/provisioning/vm/isotest.py'
696--- utah/provisioning/vm/isotest.py 2012-07-12 11:06:21 +0000
697+++ utah/provisioning/vm/isotest.py 1970-01-01 00:00:00 +0000
698@@ -1,15 +0,0 @@
699-#!/usr/bin/python
700-
701-import os
702-
703-import utah.provisioning
704-
705-isodir = '/var/cache/uath/iso'
706-
707-for iso in os.listdir(isodir):
708- isofile = os.path.join(isodir, iso)
709- m = utah.provisioning.vm.CustomVM(image=isofile)
710- print('File: ' + isofile)
711- print('Type: ' + m.installtype)
712- print('Arch: ' + m.arch)
713- print('Series: ' + m.series)
714
715=== modified file 'utah/provisioning/vm/libvirtvm.py'
716--- utah/provisioning/vm/libvirtvm.py 2012-08-20 10:20:44 +0000
717+++ utah/provisioning/vm/libvirtvm.py 2012-08-24 20:25:19 +0000
718@@ -1,4 +1,6 @@
719-#!/usr/bin/python
720+"""
721+Provide classes to provision libvirt-based virtual machines.
722+"""
723
724 import os
725 import random
726@@ -16,7 +18,6 @@
727 SSHMixin,
728 CustomInstallMixin,
729 )
730-from utah.provisioning.exceptions import UTAHProvisioningException
731 from utah.provisioning.vm.vm import VM
732 from utah.provisioning.vm.exceptions import UTAHVMProvisioningException
733 from utah import config
734@@ -40,7 +41,7 @@
735
736 def _load(self):
737 """
738- Load an existing VM
739+ Load an existing VM.
740 """
741 self.logger.info('Loading VM')
742 self.vm = self.lv.lookupByName(self.name)
743@@ -121,6 +122,9 @@
744 self.active = False
745
746 def libvirterrorhandler(self, context, err):
747+ """
748+ Log libvirt errors instead of sending them directly to the console.
749+ """
750 errorcode = err.get_error_code()
751 if errorcode in [9, 42]:
752 # We see these as part of normal operations,
753@@ -236,8 +240,7 @@
754 self.logger.info("\tThe kvm and processor-specific "
755 "kvm kernel modules are installed and loaded")
756 self.logger.error('Software virtual machine support '
757- 'will be implemented '
758- 'in a future version of UTAH')
759+ 'is implemented in the CustomVM class')
760 raise UTAHVMProvisioningException(
761 'No hardware virtual machine support available')
762 if ('Could not find' in line
763@@ -259,7 +262,7 @@
764 "'s/^vm_extra_packages=\""
765 "/vm_extra_packages=\"python-yaml /' "
766 "-i ~/.uqt_vm_tools.conf")
767- raise UTAHProvisioningException(
768+ raise UTAHVMProvisioningException(
769 'No vm-tools config file available; '
770 'more info available in '
771 + self.filehandler.baseFilename)
772@@ -344,6 +347,9 @@
773
774
775 class CustomVM(CustomInstallMixin, SSHMixin, LibvirtVM):
776+ """
777+ Install a VM from an image using libvirt direct kernel booting.
778+ """
779 def __init__(self, arch=None, boot=None, diskbus=None, disksizes=None,
780 emulator=None, image=None, initrd=None, installtype=None,
781 kernel=None, machineid=None, macs=[], name=None,
782@@ -417,6 +423,9 @@
783 self.logger.debug('CustomVM init finished')
784
785 def _createdisks(self, disksizes=None):
786+ """
787+ Create disk files if needed and build a list of them.
788+ """
789 self.logger.info('Creating disks')
790 if disksizes is None:
791 disksizes = self.disksizes
792@@ -440,6 +449,9 @@
793 self.logger.debug('Adding disk to list')
794
795 def _supportsdomaintype(self, domaintype):
796+ """
797+ Check emulator support in libvirt capabilities.
798+ """
799 capabilities = ElementTree.fromstring(self.lv.getCapabilities())
800 for guest in capabilities.iterfind('guest'):
801 for arch in guest.iterfind('arch'):
802@@ -450,6 +462,9 @@
803
804 def _installxml(self, cmdline=None, initrd=None,
805 kernel=None, tmpdir=None, xml=None):
806+ """
807+ Return the XML tree to be passed to libvirt for VM installation.
808+ """
809 self.logger.info('Creating installation XML')
810 if cmdline is None:
811 cmdline = self.cmdline
812@@ -502,11 +517,11 @@
813 #TODO: Add a cdrom if none exists
814 if disk.get('device') == 'cdrom':
815 if disk.find('source') is not None:
816- disk.find('source').set('file', self.image)
817+ disk.find('source').set('file', self.image.image)
818 self.logger.debug('Rewrote existing CD-ROM')
819 else:
820 source = ElementTree.Element('source')
821- source.set('file', self.image)
822+ source.set('file', self.image.image)
823 disk.append(source)
824 self.logger.debug('Added source to existing CD-ROM')
825 for disk in self.disks:
826@@ -559,6 +574,10 @@
827 return xmlt
828
829 def _installvm(self, lv=None, tmpdir=None, xml=None):
830+ """
831+ Install a VM, then undefine it in libvirt.
832+ The final installation will recreate the VM using the existing disks.
833+ """
834 self.logger.info('Creating VM')
835 if lv is None:
836 lv = self.lv
837@@ -581,6 +600,10 @@
838 self.logger.info('Installation complete')
839
840 def _finalxml(self, tmpdir=None, xml=None):
841+ """
842+ Create the XML to be used for the post-installation VM.
843+ This may be a transformation of the installation XML.
844+ """
845 self.logger.info('Creating final VM XML')
846 if xml is None:
847 xml = ElementTree.ElementTree(file=self.xml)
848@@ -601,6 +624,10 @@
849 return xml
850
851 def _create(self):
852+ """
853+ Create the VM, install the system, and prepare it to boot.
854+ This primarily calls functions from CustomInstallMixin and CustomVM.
855+ """
856 self.logger.info('Creating custom virtual machine')
857
858 tmpdir = tempfile.mkdtemp(prefix='/tmp/' + self.name + '_')
859@@ -653,6 +680,9 @@
860 self.active = True
861
862 def destroy(self, *args, **kw):
863+ """
864+ Remove the machine from libvirt and remove all the disk files.
865+ """
866 super(CustomVM, self).destroy(*args, **kw)
867 self.stop(force=True)
868 self.vm.undefine()
869
870=== modified file 'utah/provisioning/vm/vm.py'
871--- utah/provisioning/vm/vm.py 2012-08-20 10:20:44 +0000
872+++ utah/provisioning/vm/vm.py 2012-08-24 20:25:19 +0000
873@@ -1,13 +1,14 @@
874-#!/usr/bin/python
875+"""
876+Consolidate functions specific to virtual machine provisioning,
877+but not specific to any type of virtual machine.
878+"""
879
880 from utah.provisioning.provisioning import Machine
881
882
883 class VM(Machine):
884 """
885- Provide a generic class to provision an arbitrary VM.
886-
887- Raise exceptions for most methods.
888+ Provide a class to consolidate methods specific to virtual machines.
889 """
890 def __init__(self, *args, **kw):
891 super(VM, self).__init__(*args, **kw)
892
893=== modified file 'utah/run.py'
894--- utah/run.py 2012-08-16 13:12:51 +0000
895+++ utah/run.py 2012-08-24 20:25:19 +0000
896@@ -11,6 +11,9 @@
897
898
899 def run_tests(args, machine):
900+ """
901+ Run some tests and retrieve results.
902+ """
903 # Since we're running from a script, output INFO
904 for logger in [machine.logger] + machine.logger.handlers:
905 if logger.level > logging.INFO:
906
907=== modified file 'utah/timeout.py'
908--- utah/timeout.py 2012-08-13 20:37:09 +0000
909+++ utah/timeout.py 2012-08-24 20:25:19 +0000
910@@ -22,8 +22,8 @@
911 """
912 Run command with all other arguments for up to timeout seconds, after with
913 a UTAHTimeout exception is returned.
914- TODO: Better support for nested timeouts.
915 """
916+ #TODO: Better support for nested timeouts.
917 if command is None:
918 return
919
920@@ -101,6 +101,9 @@
921
922
923 def get_process_children(pid):
924+ """
925+ Find process children so they can be killed when the timeout expires.
926+ """
927 p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid, shell=True,
928 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
929 stdout, stderr = p.communicate()

Subscribers

People subscribed via source and target branches