45
44
disks: The disk images for the vm.
46
45
filesystems: The filesystem images for the vm.
48
result_files: A list of the files that make up the entire vm.
49
The ownership of these files will be fixed up.
51
47
optparser: Will be of interest mostly to frontends. Any sort of option
52
48
a plugin accepts will be represented in the optparser.
56
51
def __init__(self, conf=None):
57
self.hypervisor = None #: hypervisor object, representing the hypervisor the vm is destined for
61
55
self.filesystems = []
63
self.result_files = []
65
58
self._cleanup_cbs = []
67
60
#: final destination for the disk images
68
61
self.destdir = None
69
62
#: tempdir where we do all the work
70
63
self.workdir = None
71
#: mount point where the disk images will be mounted
73
64
#: directory where we build up the guest filesystem
74
65
self.tmproot = None
76
self.fsmounted = False
78
self.optparser = _MyOptParser(epilog="ubuntu-vm-builder is Copyright (C) 2007-2009 Canonical Ltd. and written by Soren Hansen <soren@canonical.com>.", usage='%prog hypervisor distro [options]')
67
self.optparser = _MyOptParser(epilog="VMBuilder is Copyright (C) 2007-2009 Canonical Ltd. and written by Soren Hansen <soren@canonical.com>.", usage='%prog hypervisor distro [options]')
79
68
self.optparser.arg_help = (('hypervisor', self.hypervisor_help), ('distro', self.distro_help))
81
70
self.confparser = ConfigParser.SafeConfigParser()
72
configuration_files = ['/etc/vmbuilder.cfg', os.path.expanduser('~/.vmbuilder.cfg')]
84
if not(os.path.isfile(conf)):
75
if not os.path.isfile(conf):
85
76
raise VMBuilderUserError('The path to the configuration file is not valid: %s.' % conf)
89
self.confparser.read(['/etc/vmbuilder.cfg', os.path.expanduser('~/.vmbuilder.cfg'), conf])
91
self._register_base_settings()
77
configuration_files += [conf]
79
self.confparser.read(configuration_files)
93
81
self.add_clean_cmd('rm', log.logfile)
95
def get_version_info(self):
97
info = vcsversion.version_info
103
83
def cleanup(self):
104
84
logging.info("Cleaning up")
105
85
while len(self._cleanup_cbs) > 0:
135
115
def setting_group(self, *args, **kwargs):
136
116
return optparse.OptionGroup(self.optparser, *args, **kwargs)
138
def _register_base_settings(self):
139
self.register_setting('-d', '--dest', dest='destdir', help='Specify the destination directory. [default: <hypervisor>-<distro>].')
140
self.register_setting('-c', '--config', type='string', help='Specify a additional configuration file')
141
self.register_setting('--debug', action='callback', callback=log.set_verbosity, help='Show debug information')
142
self.register_setting('-v', '--verbose', action='callback', callback=log.set_verbosity, help='Show progress information')
143
self.register_setting('-q', '--quiet', action='callback', callback=log.set_verbosity, help='Silent operation')
144
self.register_setting('-t', '--tmp', default=os.environ.get('TMPDIR', '/tmp'), help='Use TMP as temporary working space for image generation. Defaults to $TMPDIR if it is defined or /tmp otherwise. [default: %default]')
145
self.register_setting('--templates', metavar='DIR', help='Prepend DIR to template search path.')
146
self.register_setting('-o', '--overwrite', action='store_true', default=False, help='Force overwrite of destination directory if it already exist. [default: %default]')
147
self.register_setting('--in-place', action='store_true', default=False, help='Install directly into the filesystem images. This is needed if your $TMPDIR is nodev and/or nosuid, but will result in slightly larger file system images.')
148
self.register_setting('--tmpfs', metavar="OPTS", help='Use a tmpfs as the working directory, specifying its size or "-" to use tmpfs default (suid,dev,size=1G).')
149
self.register_setting('-m', '--mem', type='int', default=128, help='Assign MEM megabytes of memory to the guest vm. [default: %default]')
150
self.register_setting('--cpus', type='int', default=1, help='Number of virtual CPU\'s. [default: %default]')
152
group = self.setting_group('Network related options')
153
domainname = '.'.join(socket.gethostbyname_ex(socket.gethostname())[0].split('.')[1:]) or "defaultdomain"
154
group.add_option('--domain', metavar='DOMAIN', default=domainname, help='Set DOMAIN as the domain name of the guest [default: The domain of the machine running this script: %default].')
155
group.add_option('--ip', metavar='ADDRESS', default='dhcp', help='IP address in dotted form [default: %default].')
156
group.add_option('--mac', metavar='VALUE', help='MAC address of the guest [default: one will be automatically generated on first run].')
157
group.add_option('--mask', metavar='VALUE', help='IP mask in dotted form [default: based on ip setting]. Ignored if --ip is not specified.')
158
group.add_option('--net', metavar='ADDRESS', help='IP net address in dotted form [default: based on ip setting]. Ignored if --ip is not specified.')
159
group.add_option('--bcast', metavar='VALUE', help='IP broadcast in dotted form [default: based on ip setting]. Ignored if --ip is not specified.')
160
group.add_option('--gw', metavar='ADDRESS', help='Gateway (router) address in dotted form [default: based on ip setting (first valid address in the network)]. Ignored if --ip is not specified.')
161
group.add_option('--dns', metavar='ADDRESS', help='DNS address in dotted form [default: based on ip setting (first valid address in the network)] Ignored if --ip is not specified.')
162
self.register_setting_group(group)
164
118
def add_disk(self, *args, **kwargs):
165
119
"""Adds a disk image to the virtual machine"""
166
disk = Disk(self, *args, **kwargs)
120
from VMBuilder.disk import Disk
121
disk = Disk(*args, **kwargs)
167
122
self.disks.append(disk)
170
125
def add_filesystem(self, *args, **kwargs):
171
126
"""Adds a filesystem to the virtual machine"""
172
fs = Filesystem(self, *args, **kwargs)
127
from VMBuilder.disk import Filesystem
128
fs = Filesystem(*args, **kwargs)
173
129
self.filesystems.append(fs)
176
132
def call_hooks(self, func):
133
logging.info('Calling hook: %s' % (func,))
177
134
for plugin in self.plugins:
135
logging.info('Calling %s method in %s plugin.' % (func, plugin.__module__))
136
cbs = list(self._cleanup_cbs)
178
137
getattr(plugin, func)()
179
getattr(self.hypervisor, func)()
138
logging.info('Calling %s method in distro plugin.' % (func,))
180
139
getattr(self.distro, func)()
184
"Deploy" the VM, by asking the plugins in turn to deploy it.
186
If no non-hypervior and non-distro plugin accepts to deploy
187
the image, thfe hypervisor's default deployment is used.
189
Returns when the first True is returned.
191
for plugin in self.plugins:
192
if getattr(plugin, 'deploy')():
194
getattr(self.hypervisor, 'deploy')()
196
141
def set_distro(self, arg):
197
142
if arg in VMBuilder.distros.keys():
198
143
self.distro = VMBuilder.distros[arg](self)
232
170
except ConfigParser.NoOptionError, e:
236
confvalue = self.confparser.get('%s/%s' % (self.hypervisor.arg, self.distro.arg), key)
237
except ConfigParser.NoSectionError, e:
239
except ConfigParser.NoOptionError, e:
174
# confvalue = self.confparser.get('%s/%s' % (self.hypervisor.arg, self.distro.arg), key)
175
# except ConfigParser.NoSectionError, e:
177
# except ConfigParser.NoOptionError, e:
242
180
logging.debug('Returning value %s for configuration key %s' % (repr(confvalue), key))
245
183
def set_defaults(self):
247
is called to give all the plugins and the distro and hypervisor plugin a chance to set
185
is called to give all the plugins and the distro plugin a chance to set
248
186
some reasonable defaults, which the frontend then can inspect and present
250
188
multiline_split = re.compile("\s*,\s*")
251
if self.distro and self.hypervisor:
252
190
for plugin in VMBuilder._plugins:
253
191
self.plugins.append(plugin(self))
255
self.optparser.set_defaults(destdir='%s-%s' % (self.distro.arg, self.hypervisor.arg))
193
self.optparser.set_defaults(destdir='%s' % (self.distro.arg,))
257
195
(settings, dummy) = self.optparser.parse_args([])
258
196
for (k,v) in settings.__dict__.iteritems():
270
208
setattr(self, k, v)
272
self.distro.set_defaults()
273
self.hypervisor.set_defaults()
276
def ip_defaults(self):
278
is called to validate the ip configuration given and set defaults
281
logging.debug("ip: %s" % self.ip)
284
valid_mac_address = re.compile("([0-9a-f]{2}:){5}([0-9a-f]{2})", re.IGNORECASE)
285
if not valid_mac_address.search(self.mac):
286
raise VMBuilderUserError("Malformed MAC address entered: %s" % self.mac)
288
logging.debug("Valid mac given: %s" % self.mac)
290
if self.ip != 'dhcp':
291
if self.domain == '':
292
raise VMBuilderUserError('Domain is undefined and host has no domain set.')
295
numip = struct.unpack('I', socket.inet_aton(self.ip))[0]
297
raise VMBuilderUserError('%s is not a valid ip address' % self.ip)
300
ipclass = numip & 0xFF
301
if (ipclass > 0) and (ipclass <= 127):
303
elif (ipclass > 128) and (ipclass < 192):
305
elif (ipclass < 224):
308
raise VMBuilderUserError('The class of the ip address specified (%s) does not seem right' % self.ip)
310
mask = struct.unpack('I', socket.inet_aton(self.mask))[0]
312
numnet = numip & mask
315
self.net = socket.inet_ntoa( struct.pack('I', numnet ) )
317
self.bcast = socket.inet_ntoa( struct.pack('I', numnet + (mask ^ 0xFFFFFFFF)))
319
self.gw = socket.inet_ntoa( struct.pack('I', numnet + 0x01000000 ) )
323
self.mask = socket.inet_ntoa( struct.pack('I', mask ) )
325
logging.debug("net: %s" % self.net)
326
logging.debug("netmask: %s" % self.mask)
327
logging.debug("broadcast: %s" % self.bcast)
328
logging.debug("gateway: %s" % self.gw)
329
logging.debug("dns: %s" % self.dns)
331
def create_directory_structure(self):
332
"""Creates the directory structure where we'll be doing all the work
334
When create_directory_structure returns, the following attributes will be set:
336
- L{VM.destdir}: The final destination for the disk images
337
- L{VM.workdir}: The temporary directory where we'll do all the work
338
- L{VM.rootmnt}: The root mount point where all the target filesystems will be mounted
339
- L{VM.tmproot}: The directory where we build up the guest filesystem
341
..and the corresponding directories are created.
343
Additionally, L{VM.destdir} is created, which is where the files (disk images, filesystem
344
images, run scripts, etc.) will eventually be placed.
347
self.workdir = self.create_workdir()
348
self.add_clean_cmd('rm', '-rf', self.workdir)
350
logging.debug('Temporary directory: %s', self.workdir)
352
self.rootmnt = '%s/target' % self.workdir
353
logging.debug('Creating the root mount directory: %s', self.rootmnt)
354
os.mkdir(self.rootmnt)
356
self.tmproot = '%s/root' % self.workdir
357
logging.debug('Creating temporary root: %s', self.tmproot)
358
os.mkdir(self.tmproot)
360
# destdir is where the user's files will land when they're done
361
if os.path.exists(self.destdir):
363
logging.info('%s exists, and --overwrite specified. Removing..' % (self.destdir, ))
364
shutil.rmtree(self.destdir)
366
raise VMBuilderUserError('%s already exists' % (self.destdir,))
368
logging.debug('Creating destination directory: %s', self.destdir)
369
os.mkdir(self.destdir)
370
self.add_clean_cmd('rmdir', self.destdir, ignore_fail=True)
372
self.result_files.append(self.destdir)
374
def create_workdir(self):
375
"""Creates the working directory for this vm and returns its path"""
376
return tempfile.mkdtemp('', 'vmbuilder', self.tmp)
210
self.call_hooks('set_defaults')
378
212
def mount_partitions(self):
379
213
"""Mounts all the vm's partitions and filesystems below .rootmnt"""
419
253
self.distro.install_vmbuilder_log(log.logfile, self.rootmnt)
421
def preflight_check(self):
422
for opt in sum([self.confparser.options(section) for section in self.confparser.sections()], []) + [k for (k,v) in self.confparser.defaults().iteritems()]:
424
raise VMBuilderUserError('You specified a "%s" config option in a config file, but that is not valid. Perhaps you meant "%s"?' % (opt, opt.replace('-', '_')))
427
self.call_hooks('preflight_check')
429
# Check repository availability
431
testurl = self.mirror
433
testurl = 'http://archive.ubuntu.com/'
436
logging.debug('Testing access to %s' % testurl)
437
testnet = urllib.urlopen(testurl)
439
raise VMBuilderUserError('Could not connect to %s. Please check your connectivity and try again.' % testurl)
443
255
def install_file(self, path, contents=None, source=None, mode=None):
444
fullpath = '%s%s' % (self.installdir, path)
256
fullpath = '%s%s' % (self.installdir(), path)
445
257
if source and not contents:
446
258
shutil.copy(source, fullpath)