mirror of
https://github.com/kevingruesser/bootstrap-vz.git
synced 2025-08-22 18:00:35 +00:00
Simplify test harness architecture
by reducing the amount of interfacing between generic and provider specific code
This commit is contained in:
parent
287c5441ce
commit
6726df1c91
12 changed files with 144 additions and 168 deletions
|
@ -1,11 +0,0 @@
|
|||
|
||||
|
||||
def initialize_image(manifest, build_server, bootstrap_info):
|
||||
if manifest.provider['name'] == 'virtualbox':
|
||||
import vbox
|
||||
return vbox.initialize_image(manifest, build_server, bootstrap_info)
|
||||
if manifest.provider['name'] == 'ec2':
|
||||
import ami
|
||||
credentials = {'access-key': build_server.build_settings['ec2-credentials']['access-key'],
|
||||
'secret-key': build_server.build_settings['ec2-credentials']['secret-key']}
|
||||
return ami.initialize_image(manifest, credentials, bootstrap_info)
|
|
@ -1,38 +0,0 @@
|
|||
from image import Image
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def initialize_image(manifest, credentials, bootstrap_info):
|
||||
image = AmazonMachineImage(manifest, bootstrap_info._ec2['image'],
|
||||
bootstrap_info._ec2['region'], credentials)
|
||||
return image
|
||||
|
||||
|
||||
class AmazonMachineImage(Image):
|
||||
|
||||
def __init__(self, manifest, image_id, region, credentials):
|
||||
super(AmazonMachineImage, self).__init__(manifest)
|
||||
|
||||
from boto.ec2 import connect_to_region as ec2_connect
|
||||
self.ec2_connection = ec2_connect(region, aws_access_key_id=credentials['access-key'],
|
||||
aws_secret_access_key=credentials['secret-key'])
|
||||
from boto.vpc import connect_to_region as vpc_connect
|
||||
self.vpc_connection = vpc_connect(region, aws_access_key_id=credentials['access-key'],
|
||||
aws_secret_access_key=credentials['secret-key'])
|
||||
|
||||
self.ami = self.ec2_connection.get_image(image_id)
|
||||
|
||||
def destroy(self):
|
||||
log.debug('Deleting AMI')
|
||||
self.ami.deregister()
|
||||
for device, block_device_type in self.ami.block_device_mapping.items():
|
||||
self.ec2_connection.delete_snapshot(block_device_type.snapshot_id)
|
||||
del self.ami
|
||||
|
||||
@contextmanager
|
||||
def get_instance(self, instance_type):
|
||||
from ..instances.ec2 import boot_image
|
||||
with boot_image(self.ami, instance_type, self.ec2_connection, self.vpc_connection) as instance:
|
||||
yield instance
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
|
||||
class Image(object):
|
||||
|
||||
def __init__(self, manifest):
|
||||
self.manifest = manifest
|
|
@ -1,64 +0,0 @@
|
|||
from image import Image
|
||||
import virtualbox
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def initialize_image(manifest, build_server, bootstrap_info):
|
||||
from bootstrapvz.remote.build_servers.local import LocalBuildServer
|
||||
if isinstance(build_server, LocalBuildServer):
|
||||
image_path = bootstrap_info.volume.image_path
|
||||
else:
|
||||
import tempfile
|
||||
handle, image_path = tempfile.mkstemp()
|
||||
import os
|
||||
os.close(handle)
|
||||
try:
|
||||
build_server.download(bootstrap_info.volume.image_path, image_path)
|
||||
except (Exception, KeyboardInterrupt):
|
||||
os.remove(image_path)
|
||||
raise
|
||||
finally:
|
||||
build_server.delete(bootstrap_info.volume.image_path)
|
||||
image = VirtualBoxImage(manifest, image_path)
|
||||
return image
|
||||
|
||||
|
||||
class VirtualBoxImage(Image):
|
||||
|
||||
def __init__(self, manifest, image_path):
|
||||
super(VirtualBoxImage, self).__init__(manifest)
|
||||
self.image_path = image_path
|
||||
self.vbox = virtualbox.VirtualBox()
|
||||
|
||||
def open(self):
|
||||
log.debug('Opening vbox medium `{path}\''.format(path=self.image_path))
|
||||
self.medium = self.vbox.open_medium(self.image_path, # location
|
||||
virtualbox.library.DeviceType.hard_disk, # device_type
|
||||
virtualbox.library.AccessMode.read_only, # access_mode
|
||||
False) # force_new_uuid
|
||||
|
||||
def close(self):
|
||||
log.debug('Closing vbox medium `{path}\''.format(path=self.image_path))
|
||||
self.medium.close()
|
||||
|
||||
def destroy(self):
|
||||
log.debug('Deleting vbox image `{path}\''.format(path=self.image_path))
|
||||
import os
|
||||
os.remove(self.image_path)
|
||||
del self.image_path
|
||||
|
||||
@contextmanager
|
||||
def get_instance(self):
|
||||
import hashlib
|
||||
image_hash = hashlib.sha1(self.image_path).hexdigest()
|
||||
name = 'bootstrap-vz-{hash}'.format(hash=image_hash[:8])
|
||||
|
||||
self.open()
|
||||
try:
|
||||
from ..instances.vbox import boot_image
|
||||
with boot_image(name, self) as instance:
|
||||
yield instance
|
||||
finally:
|
||||
self.close()
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
|
||||
class Instance(object):
|
||||
|
||||
def __init__(self, name, image):
|
||||
self.name = name
|
||||
self.image = image
|
||||
|
||||
def boot(self):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
|
@ -1,11 +1,36 @@
|
|||
from contextlib import contextmanager
|
||||
from ..tools import waituntil
|
||||
from tests.integration.tools import waituntil
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def boot_image(image, instance_type, ec2_connection, vpc_connection):
|
||||
def boot_image(manifest, build_server, bootstrap_info, instance_type=None):
|
||||
|
||||
credentials = {'access-key': build_server.build_settings['ec2-credentials']['access-key'],
|
||||
'secret-key': build_server.build_settings['ec2-credentials']['secret-key']}
|
||||
from boto.ec2 import connect_to_region as ec2_connect
|
||||
ec2_connection = ec2_connect(bootstrap_info._ec2['region'],
|
||||
aws_access_key_id=credentials['access-key'],
|
||||
aws_secret_access_key=credentials['secret-key'])
|
||||
from boto.vpc import connect_to_region as vpc_connect
|
||||
vpc_connection = vpc_connect(bootstrap_info._ec2['region'],
|
||||
aws_access_key_id=credentials['access-key'],
|
||||
aws_secret_access_key=credentials['secret-key'])
|
||||
|
||||
if manifest.volume['backing'] == 'ebs':
|
||||
from images import EBSImage
|
||||
image = EBSImage(bootstrap_info._ec2['image'], ec2_connection)
|
||||
|
||||
try:
|
||||
with run_instance(image, instance_type, ec2_connection, vpc_connection) as instance:
|
||||
yield instance
|
||||
finally:
|
||||
image.destroy()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def run_instance(image, instance_type, ec2_connection, vpc_connection):
|
||||
|
||||
with create_env(ec2_connection, vpc_connection) as boot_env:
|
||||
|
||||
|
@ -18,8 +43,8 @@ def boot_image(image, instance_type, ec2_connection, vpc_connection):
|
|||
instance = None
|
||||
try:
|
||||
log.debug('Booting ec2 instance')
|
||||
reservation = image.run(instance_type=instance_type,
|
||||
subnet_id=boot_env['subnet_id'])
|
||||
reservation = image.ami.run(instance_type=instance_type,
|
||||
subnet_id=boot_env['subnet_id'])
|
||||
[instance] = reservation.instances
|
||||
instance.add_tag('Name', 'bootstrap-vz test instance')
|
||||
|
19
tests/integration/providers/ec2/images.py
Normal file
19
tests/integration/providers/ec2/images.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AmazonMachineImage(object):
|
||||
|
||||
def __init__(self, image_id, ec2_connection):
|
||||
self.ec2_connection = ec2_connection
|
||||
self.ami = self.ec2_connection.get_image(image_id)
|
||||
|
||||
|
||||
class EBSImage(AmazonMachineImage):
|
||||
|
||||
def destroy(self):
|
||||
log.debug('Deleting AMI')
|
||||
self.ami.deregister()
|
||||
for device, block_device_type in self.ami.block_device_mapping.items():
|
||||
self.ec2_connection.delete_snapshot(block_device_type.snapshot_id)
|
||||
del self.ami
|
55
tests/integration/providers/virtualbox/__init__.py
Normal file
55
tests/integration/providers/virtualbox/__init__.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from contextlib import contextmanager
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def boot_image(manifest, build_server, bootstrap_info):
|
||||
from bootstrapvz.remote.build_servers.local import LocalBuildServer
|
||||
if isinstance(build_server, LocalBuildServer):
|
||||
image_path = bootstrap_info.volume.image_path
|
||||
else:
|
||||
import tempfile
|
||||
handle, image_path = tempfile.mkstemp()
|
||||
import os
|
||||
os.close(handle)
|
||||
try:
|
||||
build_server.download(bootstrap_info.volume.image_path, image_path)
|
||||
except (Exception, KeyboardInterrupt):
|
||||
os.remove(image_path)
|
||||
raise
|
||||
finally:
|
||||
build_server.delete(bootstrap_info.volume.image_path)
|
||||
|
||||
from image import VirtualBoxImage
|
||||
image = VirtualBoxImage(image_path)
|
||||
|
||||
import hashlib
|
||||
image_hash = hashlib.sha1(image_path).hexdigest()
|
||||
instance_name = 'bootstrap-vz-{hash}'.format(hash=image_hash[:8])
|
||||
|
||||
try:
|
||||
image.open()
|
||||
try:
|
||||
with run_instance(image, instance_name, manifest) as instance:
|
||||
yield instance
|
||||
finally:
|
||||
image.close()
|
||||
finally:
|
||||
image.destroy()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def run_instance(image, instance_name, manifest):
|
||||
from instance import VirtualBoxInstance
|
||||
instance = VirtualBoxInstance(image, instance_name,
|
||||
manifest.system['architecture'], manifest.system['release'])
|
||||
try:
|
||||
instance.create()
|
||||
try:
|
||||
instance.boot()
|
||||
yield instance
|
||||
finally:
|
||||
instance.shutdown()
|
||||
finally:
|
||||
instance.destroy()
|
27
tests/integration/providers/virtualbox/image.py
Normal file
27
tests/integration/providers/virtualbox/image.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import virtualbox
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VirtualBoxImage(object):
|
||||
|
||||
def __init__(self, image_path):
|
||||
self.image_path = image_path
|
||||
self.vbox = virtualbox.VirtualBox()
|
||||
|
||||
def open(self):
|
||||
log.debug('Opening vbox medium `{path}\''.format(path=self.image_path))
|
||||
self.medium = self.vbox.open_medium(self.image_path, # location
|
||||
virtualbox.library.DeviceType.hard_disk, # device_type
|
||||
virtualbox.library.AccessMode.read_only, # access_mode
|
||||
False) # force_new_uuid
|
||||
|
||||
def close(self):
|
||||
log.debug('Closing vbox medium `{path}\''.format(path=self.image_path))
|
||||
self.medium.close()
|
||||
|
||||
def destroy(self):
|
||||
log.debug('Deleting vbox image `{path}\''.format(path=self.image_path))
|
||||
import os
|
||||
os.remove(self.image_path)
|
||||
del self.image_path
|
|
@ -1,32 +1,20 @@
|
|||
from instance import Instance
|
||||
import virtualbox
|
||||
from contextlib import contextmanager
|
||||
from ..tools import waituntil
|
||||
from tests.integration.tools import waituntil
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def boot_image(name, image):
|
||||
instance = VirtualBoxInstance(name, image)
|
||||
try:
|
||||
instance.create()
|
||||
try:
|
||||
instance.boot()
|
||||
yield instance
|
||||
finally:
|
||||
instance.shutdown()
|
||||
finally:
|
||||
instance.destroy()
|
||||
|
||||
|
||||
class VirtualBoxInstance(Instance):
|
||||
class VirtualBoxInstance(object):
|
||||
|
||||
cpus = 1
|
||||
memory = 256
|
||||
|
||||
def __init__(self, name, image):
|
||||
super(VirtualBoxInstance, self).__init__(name, image)
|
||||
def __init__(self, image, name, arch, release):
|
||||
self.image = image
|
||||
self.name = name
|
||||
self.arch = arch
|
||||
self.release = release
|
||||
self.vbox = virtualbox.VirtualBox()
|
||||
manager = virtualbox.Manager()
|
||||
self.session = manager.get_session()
|
||||
|
@ -35,7 +23,7 @@ class VirtualBoxInstance(Instance):
|
|||
log.debug('Creating vbox machine `{name}\''.format(name=self.name))
|
||||
# create machine
|
||||
os_type = {'x86': 'Debian',
|
||||
'amd64': 'Debian_64'}.get(self.image.manifest.system['architecture'])
|
||||
'amd64': 'Debian_64'}.get(self.arch)
|
||||
self.machine = self.vbox.create_machine(settings_file='', name=self.name,
|
||||
groups=[], os_type_id=os_type, flags='')
|
||||
self.machine.cpu_count = self.cpus
|
||||
|
@ -71,12 +59,12 @@ class VirtualBoxInstance(Instance):
|
|||
def boot(self):
|
||||
log.debug('Booting vbox machine `{name}\''.format(name=self.name))
|
||||
self.machine.launch_vm_process(self.session, 'headless').wait_for_completion(-1)
|
||||
from ..tools import read_from_socket
|
||||
from tests.integration.tools import read_from_socket
|
||||
# Gotta figure out a more reliable way to check when the system is done booting.
|
||||
# Maybe bootstrapped unit test images should have a startup script that issues
|
||||
# a callback to the host.
|
||||
from bootstrapvz.common.tools import get_codename
|
||||
if get_codename(self.image.manifest.system['release']) in ['squeeze', 'wheezy']:
|
||||
if get_codename(self.release) in ['squeeze', 'wheezy']:
|
||||
termination_string = 'INIT: Entering runlevel: 2'
|
||||
else:
|
||||
termination_string = 'Debian GNU/Linux'
|
|
@ -26,13 +26,10 @@ def boot_manifest(manifest_data, boot_vars={}):
|
|||
bootstrap_info = connection.run(manifest)
|
||||
|
||||
log.info('Creating and booting instance')
|
||||
from ..images import initialize_image
|
||||
image = initialize_image(manifest, build_server, bootstrap_info)
|
||||
try:
|
||||
with image.get_instance(**boot_vars) as instance:
|
||||
yield instance
|
||||
finally:
|
||||
image.destroy()
|
||||
import importlib
|
||||
provider_module = importlib.import_module('tests.integration.providers.' + manifest.provider['name'])
|
||||
with provider_module.boot_image(manifest, build_server, bootstrap_info, **boot_vars) as instance:
|
||||
yield instance
|
||||
|
||||
|
||||
def waituntil(predicate, timeout=5, interval=0.05):
|
||||
|
|
Loading…
Add table
Reference in a new issue