def start_test_environment(test_id=None, config={}):
    """
    Creates the test environment:
      - Run dependency containers
      - Provision dependency containers, if necessary
      - Run the microservices
      - Run the tests
    :param test_id: identifier of the test
    :type test_id: string
    :param config: Config detailing which versions and services to provision
    :type config: dict
    :return: None
    """

    if not test_id:
        test_id = gen_salt(5)

    if not config:
        with open('mc/.mc.yml') as yaml_file:
            config = load_yaml_ordered(yaml_file)

    services = config.get('services')
    dependencies = config.get('dependencies')

    containers = {}

    # Deploy
    logger.info('Starting cluster dependencies...')
    for d in dependencies:
        logger.info('... {}'.format(d['image']))

        builder = docker_runner_factory(image=d['image'])(
            image=d['image'],
            name="{}-{}".format(d['name'], test_id),
            build_requirements={r: containers[r] for r in (d['build_requirements'] if d.get('build_requirements', False) else [])}
        )
        builder.start()
        containers[d['name']] = builder

    # Provision
    logger.info('Provisioning cluster dependencies...')
    for d in dependencies:
        containers[d['name']].provision(
            services=[s['name'].replace('-services', '').replace('-service', '').replace('_service', '') for s in services],
            requirements={r: containers[r] for r in d.get('requirements', [])}
        )
        logger.info('... {}'.format(d['image']))

    # Required values for services that are based on dependencies
    # We are using the docker0 IP for localhost (of host) in the container
    service_environment = dict(
        CONSUL_HOST=DOCKER_BRIDGE,
        CONSUL_PORT=containers['consul'].running_port,
        ENVIRONMENT='staging'
    )

    logger.info('Starting services...')
    for s in services:

        logger.info('... {}/{}:{}'.format(
            s['repository'],
            s['name'],
            s['tag']
        ))

        service_environment['SERVICE'] = s['name']

        image = '{repository}/{service}:{tag}'.format(
            repository=s['repository'],
            service=s['name'],
            tag=s['tag']
        )
        try:
            builder = docker_runner_factory('gunicorn')(
                image=image,
                name='{}-{}'.format(s['name'], test_id),
                environment=service_environment
            )
            builder.start()
            containers[s['name']] = builder

        except errors.NotFound as error:
            logger.error('Service not found, skipping: {}, {}'.format(s, error))
def run_ci_test(test_id=None, config={}, **kwargs):
    """
    Spin up services and dependencies, run continuous integratioh test, tear down services and dependencies.
    :param test_id: unique identifier
    :type test_id: basestring

    :param config: Config detailing which versions and services to provision
    :type config: dict

    :param kwargs: keyword arguments
    """
    test_id = gen_salt(5) if not test_id else test_id

    if not config:
        with open('mc/.mc.yml') as yaml_file:
            config = load_yaml_ordered(yaml_file)

    services = config.get('services')
    dependencies = config.get('dependencies')
    tests = config.get('tests')

    containers = {}

    # Deploy
    logger.info('Starting cluster dependencies...')
    for d in dependencies:
        logger.info('... {}'.format(d['image']))

        builder = docker_runner_factory(image=d['image'])(
            image=d['image'],
            name="{}-{}".format(d['name'], test_id),
            build_requirements={r: containers[r] for r in (d['build_requirements'] if d.get('build_requirements', False) else [])}
        )
        builder.start()
        containers[d['name']] = builder

    # Provision
    logger.info('Provisioning cluster dependencies...')
    try:
        for d in dependencies:
            containers[d['name']].provision(
                services=[s['name'].replace('-services', '').replace('-service', '').replace('_service', '') for s in services],
                requirements={r: containers[r] for r in d.get('requirements', [])}
            )
            logger.info('... {}'.format(d['image']))

        # Required values for services that are based on dependencies
        # We are using the docker0 IP for localhost (of host) in the container
        service_environment = dict(
            CONSUL_HOST=DOCKER_BRIDGE,
            CONSUL_PORT=containers['consul'].running_port,
            ENVIRONMENT='staging'
        )
    except Exception as error:
        logger.warning('Unexpected error, shutting down ... {}'.format(error))
        for container, name in containers.iteritems():
            logger.info('... {}'.format(name))
            container.teardown()

    logger.info('Starting services...')
    for s in services:

        logger.info('... {}/{}:{}'.format(
            s['repository'],
            s['name'],
            s['tag']
        ))

        service_environment['SERVICE'] = s['name']

        image = '{repository}/{service}:{tag}'.format(
            repository=s['repository'],
            service=s['name'],
            tag=s['tag']
        )
        try:
            builder = docker_runner_factory('gunicorn')(
                image=image,
                name='{}-{}'.format(s['name'], test_id),
                environment=service_environment
            )
            builder.start()
            containers[s['name']] = builder

        except errors.NotFound as error:
            logger.error('Service not found, skipping: {}, {}'.format(s, error))

    # Run the tests
    try:
        logger.info('Running tests: {} [ID: {}]'.format(tests, test_id))
        tests = TestRunner(test_id=test_id, test_services=tests)
        tests.start()
    except Exception as error:
        logger.warning('Unexpected error, continuing to shutdown test: {}'.format(error))

    # Shutdown the test cluster
    logger.info('Shutting down services and dependencies...')
    for name, container in containers.iteritems():
        logger.info('... {}'.format(name))
        try:
            container.teardown()
        except Exception as error:
            logger.warning('... Could not stop: {} '.format(error))