1
0
mirror of https://github.com/gryf/boxpy.git synced 2026-02-06 16:25:56 +01:00

Changing approach with detecting if cloud init finished.

Till now, boxpy was based on the fact, that there was power_state
section, and there was a check if VM is down already. That approach have
their own issues.

Now, there will be no more power-off, there is a check by using
`cloud-init status` command through ssh.

Cleanup/destroy parts has needed some modification, so that there will
be no leftovers from cloud init ISO image.

And finally, there was some tweaks for user-data cloud-init part (mainly
for ssh handling), so that `cloud-init status` will not report phony
errors.
This commit is contained in:
2021-06-26 15:40:31 +02:00
parent cc4b4da253
commit a56b76f16d
2 changed files with 55 additions and 30 deletions

View File

@@ -116,15 +116,10 @@ Default user-script looks as follows:
gecos: ubuntu gecos: ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin groups: users, admin
power_state:
mode: poweroff
timeout: 10
condition: True
It is really simple, and use ``string.Template`` for exchanging token It is really simple, and use ``string.Template`` for exchanging token
``$ssh_key`` with default, or provided public key, so that you will be able to ``$ssh_key`` with default, or provided public key, so that you will be able to
log in into the VM using that key. Section ``power_state`` is used internally log in into the VM using that key.
for making sure the cloud-init finish up and the VM will be started again.
Note, that you need to be extra careful regarding ``$`` sign. As explained Note, that you need to be extra careful regarding ``$`` sign. As explained
above ``$ssh_key`` will be used as a "variable" for the template to substitute above ``$ssh_key`` will be used as a "variable" for the template to substitute
@@ -145,10 +140,10 @@ pass filenames to the custom config, instead of filling up
filename: /path/to/local/file.txt filename: /path/to/local/file.txt
during processing this file, boxpy will look for ``filename`` key in the yaml during processing this file, boxpy will look for ``filename`` key in the yaml
file for the ``write_files`` sections, and it will remove that key read the file for the ``write_files`` sections, and it will remove that key, read the
file and put its contents under ``content`` key. What is more important, that file and put its contents under ``content`` key. What is more important, that
will be done after template processing, so that there will be no interference will be done after template processing, so there will be no interference for
for possible ``$`` characters. possible ``$`` characters.
What is more interesting is the fact, that you could use whatever cloud-init What is more interesting is the fact, that you could use whatever cloud-init
accepts, and a special section, for keeping configuration, so that you don't accepts, and a special section, for keeping configuration, so that you don't
@@ -185,6 +180,18 @@ initialized, just to make you an idea, what could be done with it.
You can find some real world examples of the yaml cloud-init files that I use You can find some real world examples of the yaml cloud-init files that I use
in examples directory. in examples directory.
There is special section ``boxpy_data``, where you can place all the
configuration for the VM. Keys are the same as in ``create`` command options.
There is one additional key ``advanced`` which for now can be used for
configuration additional NIC for virtual machine, i.e:
.. code:: yaml
boxpy_data:
advanced:
nic2: intnet
License License
------- -------

60
box.py
View File

@@ -34,10 +34,9 @@ users:
gecos: ${realname} gecos: ${realname}
sudo: ALL=(ALL) NOPASSWD:ALL sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin groups: users, admin
power_state: no_ssh_fingerprints: true
mode: poweroff ssh:
timeout: 10 emit_keys_to_console: false
condition: True
boxpy_data: boxpy_data:
cpus: 1 cpus: 1
disk_size: 10240 disk_size: 10240
@@ -579,12 +578,22 @@ class VBoxManage:
return Run(['vboxmanage', 'list', 'runningvms']).stdout return Run(['vboxmanage', 'list', 'runningvms']).stdout
def destroy(self): def destroy(self):
self.get_vm_info()
if not self.vm_info:
LOG.fatal("Cannot remove VM \"%s\" - it doesn't exist.",
self.name_or_uuid)
return 4
LOG.info('Removing VM %s.', self.name_or_uuid) LOG.info('Removing VM %s.', self.name_or_uuid)
self.poweroff(silent=True) self.poweroff(silent=True)
time.sleep(1) # wait a bit, for VM shutdown to complete
# detach cloud image.
self.storageattach('IDE', 1, 'dvddrive', 'none')
self.closemedium('dvd', self.vm_info['iso_path'])
if Run(['vboxmanage', 'unregistervm', self.name_or_uuid, if Run(['vboxmanage', 'unregistervm', self.name_or_uuid,
'--delete']).returncode != 0: '--delete']).returncode != 0:
LOG.fatal('Removing VM "%s" failed', self.name_or_uuid) LOG.fatal('Removing VM "%s" failed', self.name_or_uuid)
raise BoxVBoxFailure() return 7
def create(self, cpus, memory, port=None): def create(self, cpus, memory, port=None):
LOG.info('Creating VM %s.', self.name_or_uuid) LOG.info('Creating VM %s.', self.name_or_uuid)
@@ -972,6 +981,7 @@ def vmcreate(args, conf=None):
iso = IsoImage(conf) iso = IsoImage(conf)
path_to_iso = iso.get_generated_image() path_to_iso = iso.get_generated_image()
vbox.setextradata('iso_path', path_to_iso)
vbox.storageattach('SATA', 0, 'hdd', path_to_disk) vbox.storageattach('SATA', 0, 'hdd', path_to_disk)
vbox.storageattach('IDE', 1, 'dvddrive', path_to_iso) vbox.storageattach('IDE', 1, 'dvddrive', path_to_iso)
@@ -986,34 +996,42 @@ def vmcreate(args, conf=None):
# give VBox some time to actually change the state of the VM before query # give VBox some time to actually change the state of the VM before query
time.sleep(3) time.sleep(3)
def _cleanup(vbox, iso, image, path_to_iso):
time.sleep(1) # wait a bit, for VM shutdown to complete
vbox.storageattach('IDE', 1, 'dvddrive', 'none')
vbox.closemedium('dvd', path_to_iso)
iso.cleanup()
image.cleanup()
# than, let's try to see if boostraping process has finished # than, let's try to see if boostraping process has finished
LOG.info('Waiting for cloud init to finish ', end='') LOG.info('Waiting for cloud init to finish ', end='')
cmd = ['ssh', '-o', 'StrictHostKeyChecking=no',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'ConnectTimeout=2',
'-i', conf.ssh_key_path[:-4],
f'ssh://{DISTROS[conf.distro]["username"]}'
f'@localhost:{vbox.vm_info["port"]}', 'cloud-init status']
try: try:
while True: while True:
if vbox.vm_info['uuid'] in vbox.get_running_vms(): out = Run(cmd).stdout
LOG.debug2('Out: %s', out)
if (not out) or ('status' in out and 'running' in out):
LOG.info('.', end='') LOG.info('.', end='')
sys.stdout.flush() sys.stdout.flush()
time.sleep(3) time.sleep(3)
else: continue
LOG.info(' done.')
break LOG.info(' done.')
break
out = out.split(':')[1].strip()
if out != 'done':
LOG.warning('Cloud init finished with "%s" status. You can log '
'in and investigate.', out)
except KeyboardInterrupt: except KeyboardInterrupt:
LOG.warning('\nIterrupted, cleaning up.') LOG.warning('\nIterrupted, cleaning up.')
vbox.poweroff(silent=True) iso.cleanup()
_cleanup(vbox, iso, image, path_to_iso) image.cleanup()
vbox.destroy() vbox.destroy()
return 1 return 1
# dettach ISO image # cleanup
_cleanup(vbox, iso, image, path_to_iso) iso.cleanup()
vbox.poweron() image.cleanup()
# reread config to update fields # reread config to update fields
conf = Config(args, vbox) conf = Config(args, vbox)