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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Javier Collado (community) | Approve | ||
Review via email:
|
Commit message
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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() |
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.