Implement docker integration test provider and docker tests

Also make build_server.remote_command() public. It's quite useful.
This commit is contained in:
Anders Ingemann 2015-12-13 19:26:11 +01:00
parent 7502b2a756
commit 072969065c
8 changed files with 162 additions and 4 deletions

View file

@ -4,7 +4,7 @@ Changelog
2015-12-13
----------
Anders Ingemann:
* Docker provider implemented
* Docker provider implemented (including integration testing harness & tests)
* minimize_size: Added various size reduction options for dpkg and apt
* Removed image section in manifest.
Provider specific options have been moved to the provider section.

View file

@ -185,6 +185,10 @@ This is useful only when running integration tests.
ec2-credentials:
access-key: AFAKEACCESSKEYFORAWS
secret-key: thes3cr3tkeyf0ryourawsaccount/FS4d8Qdva
docker:
machine: default
* ``ec2-credentials`` contains the access key and secret key used to boot
an EC2 AMI.
* ``docker.machine`` The docker machine on which an image built for docker
should run.

View file

@ -26,6 +26,7 @@ definitions:
- virtualbox
- ec2-ebs
- ec2-s3
- docker
build_settings:
type: object
@ -60,6 +61,11 @@ definitions:
access-key: {type: string}
secret-key: {type: string}
additional_properties: false
docker:
type: object
properties:
machine: {type: string}
additional_properties: false
ssh:
type: object

View file

@ -80,16 +80,16 @@ class RemoteBuildServer(BuildServer):
'build server `{name}\' to `{dst}\''
.format(src=src, dst=dst, name=self.name))
# Make sure we can read the file as {user}
self._remote_command(['sudo', 'chown', self.username, src])
self.remote_command(['sudo', 'chown', self.username, src])
src_arg = '{user}@{host}:{path}'.format(user=self.username, host=self.address, path=src)
log_check_call(['scp', '-i', self.keyfile, '-P', str(self.port),
src_arg, dst])
def delete(self, path):
log.debug('Deleting file `{path}\' on build server `{name}\''.format(path=path, name=self.name))
self._remote_command(['sudo', 'rm', path])
self.remote_command(['sudo', 'rm', path])
def _remote_command(self, command):
def remote_command(self, command):
ssh_cmd = ['ssh', '-i', self.keyfile,
'-p', str(self.port),
self.username + '@' + self.address,

View file

@ -0,0 +1,29 @@
from manifests import merge_manifest_data
from tools import boot_manifest
partials = {'docker': '''
provider:
name: docker
virtualization: hvm
dockerfile: CMD /bin/bash
bootstrapper:
variant: minbase
system:
bootloader: none
volume:
backing: folder
partitions:
type: none
root:
filesystem: ext4
size: 1GiB
''',
}
def test_stable():
std_partials = ['base', 'stable64']
custom_partials = [partials['docker']]
manifest_data = merge_manifest_data(std_partials, custom_partials)
with boot_manifest(manifest_data) as instance:
print('\n'.join(instance.run(['echo', 'test'])))

View file

@ -0,0 +1,7 @@
Docker
------
Dependencies
~~~~~~~~~~~~
The host machine running the integration tests must have docker installed.

View file

@ -0,0 +1,57 @@
from contextlib import contextmanager
import logging
log = logging.getLogger(__name__)
@contextmanager
def boot_image(manifest, build_server, bootstrap_info):
image_id = None
try:
import os
from bootstrapvz.common.tools import log_check_call
docker_machine = build_server.run_settings.get('docker', {}).get('machine', None)
docker_env = os.environ.copy()
if docker_machine is not None:
cmd = ('eval "$(docker-machine env {machine})" && '
'echo $DOCKER_HOST && echo $DOCKER_CERT_PATH && echo $DOCKER_TLS_VERIFY'
.format(machine=docker_machine))
[docker_host, docker_cert_path, docker_tls] = log_check_call([cmd], shell=True)
docker_env['DOCKER_TLS_VERIFY'] = docker_tls
docker_env['DOCKER_HOST'] = docker_host
docker_env['DOCKER_CERT_PATH'] = docker_cert_path
docker_env['DOCKER_MACHINE_NAME'] = docker_machine
from bootstrapvz.remote.build_servers.local import LocalBuildServer
image_id = bootstrap_info._docker['image_id']
if not isinstance(build_server, LocalBuildServer):
import tempfile
handle, image_path = tempfile.mkstemp()
os.close(handle)
remote_image_path = os.path.join('/tmp', image_id)
try:
log.debug('Saving remote image to file')
build_server.remote_command([
'sudo', 'docker', 'save',
'--output=' + remote_image_path,
image_id,
])
log.debug('Downloading remote image')
build_server.download(remote_image_path, image_path)
log.debug('Importing image')
log_check_call(['docker', 'load', '--input=' + image_path], env=docker_env)
except (Exception, KeyboardInterrupt):
raise
finally:
log.debug('Deleting exported image from build server and locally')
build_server.delete(remote_image_path)
os.remove(image_path)
log.debug('Deleting image from build server')
build_server.remote_command(['sudo', 'docker', 'rmi',
bootstrap_info._docker['image_id']])
from image import Image
with Image(image_id, docker_env) as container:
yield container
finally:
if image_id is not None:
log.debug('Deleting image')
log_check_call(['docker', 'rmi', image_id], env=docker_env)

View file

@ -0,0 +1,55 @@
from bootstrapvz.common.tools import log_check_call
import logging
log = logging.getLogger(__name__)
class Image(object):
def __init__(self, image_id, docker_env):
self.image_id = image_id
self.docker_env = docker_env
def __enter__(self):
self.container = Container(self.image_id, self.docker_env)
self.container.create()
try:
self.container.start()
except:
self.container.destroy()
raise
return self.container
def __exit__(self, exc_type, exc_value, traceback):
try:
self.container.stop()
self.container.destroy()
except Exception as e:
log.exception(e)
class Container(object):
def __init__(self, image_id, docker_env):
self.image_id = image_id
self.docker_env = docker_env
def create(self):
log.debug('Creating container')
[self.container_id] = log_check_call(['docker', 'create', '--tty=true', self.image_id], env=self.docker_env)
def start(self):
log.debug('Starting container')
log_check_call(['docker', 'start', self.container_id], env=self.docker_env)
def run(self, command):
log.debug('Running command in container')
return log_check_call(['docker', 'exec', self.container_id] + command, env=self.docker_env)
def stop(self):
log.debug('Stopping container')
log_check_call(['docker', 'stop', self.container_id], env=self.docker_env)
def destroy(self):
log.debug('Deleting container')
log_check_call(['docker', 'rm', self.container_id], env=self.docker_env)
del self.container_id