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