Пример #1
0
    def build(self, update_application=False, quiet=False):
        """
        Create a virtual machine image of the configured host.

        Args:
            update_application (bool): If applicable, update the application
                definition Yaml file to use this image as host base for the
                selected provider. Warning, this will reset any yaml file
                formatting and comments.
            quiet (bool): If True, hide outputs.

        Returns:
            str: Image ID or path (Depending provider)
        """
        manifest = self._packer.build(quiet=quiet)
        image = self._packer.get_artifact(manifest)

        if update_application and self._application_yaml:
            application = Application(self._application_yaml)
            try:
                section = application['package'][self._provider]
            except KeyError:
                section = application['package'][self._provider] = dict()

            section['type'] = 'vm_image'
            section['name'] = image
            application.save()

        return image
Пример #2
0
def test_web_service_integration():
    """
    Test web service integration.
    """
    from accelpy._common import accelize_ws_session
    from accelpy._application import Application
    from random import randint

    # Use dev environment
    request_endpoint = accelize_ws_session._ENDPOINT
    accelize_ws_session._ENDPOINT = 'https://master.devmetering.accelize.com'

    product_id = 'accelize.com/accelpy/ci'
    version = f'{randint(0, 255)}.{randint(0, 255)}.{randint(0, 255)}'
    application = f'{product_id}:{version}'

    definition = dict(application=dict(product_id=product_id,
                                       type='container_service',
                                       version=version),
                      fpga=dict(image='nothing'),
                      package=dict(name='nothing', type='container_image'),
                      accelize_drm=dict(use_service=False))

    try:
        scr_app = Application(definition)

        # Test: push
        scr_app.push()

        # Test: Get
        srv_app = Application.from_id(application)
        assert scr_app._definition == srv_app._definition

        # Test: List
        assert product_id in Application.list()

        # Test: List with prefix
        assert product_id in Application.list('accelize.com/accelpy')

        # Test: List version
        assert version in Application.list_versions(product_id)

        # Test: List version with prefix
        assert version in Application.list_versions(product_id,
                                                    version.split('.', 1)[0])

        # Test: Delete
        srv_app.delete()
        assert version not in Application.list_versions(product_id)

    finally:
        try:
            srv_app.delete()
        except Exception:
            pass
        accelize_ws_session._endpoint = request_endpoint
Пример #3
0
def test_host_mocked(tmpdir):
    """
    Test existing host provisioning on a mocked host.

    Args:
        tmpdir (py.path.local) tmpdir pytest fixture
    """
    from os import environ
    from os.path import dirname, join
    import accelpy._host as accelpy_host
    from accelpy._host import Host
    from accelpy._application import Application

    environ['ACCELPY_DEBUG'] = 'True'

    mocked_app_path = join(dirname(__file__), 'host_mock.yml')
    app = join(dirname(__file__), 'host_app.yml')

    # Mock config dir
    accelpy_host_config_dir = accelpy_host.CONFIG_DIR
    config_dir = tmpdir.join('config').ensure(dir=True)
    accelpy_host.CONFIG_DIR = str(config_dir)

    # Mock user_config dir
    user_config = tmpdir.join('user_config').ensure(dir=True)

    # Mock an existing host with a virtual machine
    mocked_app = Application(mocked_app_path)
    mocked_provider = mocked_app.providers.pop()
    mocked_host = Host(name=get_name(mocked_app),
                       application=mocked_app_path,
                       provider=mocked_provider)

    try:
        # Create host
        mocked_host.apply(quiet=True)

        # Create the existing host Terraform configuration override
        user_config.join('host.user_override.tf').write_text('\n'.join(
            ('locals {', f'  host_ip          = ["{mocked_host.public_ip}"]',
             f'  ssh_key_pem      = "{mocked_host.ssh_private_key}"',
             f'  remote_user      = "******"',
             f'  require_ask_pass = false', '}')),
                                                             encoding='utf-8')

        # Provision existing host
        with Host(application=app,
                  user_config=user_config,
                  provider=f'host,{mocked_provider.replace(",", "-")}',
                  destroy_on_exit=True,
                  keep_config=False) as host:
            host.apply()

    # Restore mocked configuration
    finally:
        try:
            mocked_host.destroy(quiet=True, delete=True)
        finally:
            accelpy_host.CONFIG_DIR = accelpy_host_config_dir
            del environ['ACCELPY_DEBUG']
Пример #4
0
def _provider_completer(prefix, parsed_args, **_):
    """
    Autocomplete "accelpy init --provider"

    Args:
        prefix (str): Provider prefix to filter.
        parsed_args (argparse.Namespace): CLI arguments.

    Returns:
        list of str: providers
    """
    application = parsed_args.application
    if application is None:
        _completer_warn('Set "--application"/"-a" argument first to allow '
                        '"--provider"/"-p" argument autocompletion.')
        return

    # First try to get providers from cache
    from os.path import isfile, abspath
    from accelpy._common import get_cli_cache, set_cli_cache

    application = abspath(application) if isfile(application) else application
    cached = f'providers|{application}'
    providers = get_cli_cache(cached)

    # Else get providers from application and cache them
    if not providers:
        from accelpy._application import Application
        providers = Application(application).providers
        set_cli_cache(cached, list(providers))

    # Filter with prefix
    return (provider for provider in providers if provider.startswith(prefix))
Пример #5
0
def parametrize_application_yml(argvalues, ids):
    """
    Define parameters for testable application definition.

    Args:
        argvalues (list): Parametrize args values
        ids (list): Parametrize ID
    """
    from accelpy._application import Application

    with scandir(dirname(realpath(__file__))) as entries:
        print(dirname(realpath(__file__)))
        for entry in entries:
            name, ext = splitext(entry.name)
            if ext == '.yml':
                print(entry.path)
                app = Application(entry.path)
                print(app, app.environments)
                if 'test' not in app._definition and not app.environments:
                    continue

                name = name.split("_", 1)[1].replace('_', '-')
                for provider in app.environments:
                    ids.append(f'{name}_{provider.replace(",", "-")}')
                    argvalues.append(dict(path=entry.path, provider=provider))
Пример #6
0
def _action_push(args):
    """
    Push application definition.

    Args:
        args (argparse.Namespace): CLI arguments.
    """
    from accelpy._application import Application
    Application(args.file).push()
Пример #7
0
    def _application(self):
        """
        Application definition.

        Returns:
            accelpy._application.Application: Definition
        """
        if not self._application_definition:
            self._application_definition = Application(self._application_yaml)

        return self._application_definition
Пример #8
0
def _get_product_ids(prefix):
    """
    Get products IDs from web server.

    Args:
        prefix (str): Application prefix to filter.

    Returns:
        list of str: Product ids.
    """
    from accelpy._application import Application
    return Application.list(prefix)
Пример #9
0
    def _init_application_definition(self, application):
        """
        Get remote or local application definition and save it locally.

        Args:
            application (str or path-like object):
                Application or path to application definition file.
        """
        dst_path = join(self._config_dir, 'application.yml')

        # Try if application is a local path
        src_path = realpath(fsdecode(application))

        # Link local definition file in configuration directory
        if isfile(src_path):
            return symlink(src_path, dst_path)

        # Lazy import: May not be used all time
        from accelpy._application import Application

        # Get application definition from accelize server
        Application.from_id(application).save(dst_path)
Пример #10
0
    def _application(self):
        """
        Application definition.

        Returns:
            accelpy._application.Application: Definition
        """
        if not self._application_definition:
            # Lazy import: May not be used all time
            from accelpy._application import Application

            self._application_definition = Application(
                realpath(join(self._config_dir, 'application.yml')))

        return self._application_definition
Пример #11
0
def _get_versions(prefix):
    """
    Get versions from web server.

    Args:
        prefix (str): Application prefix to filter.

    Returns:
        list of str: Versions.
    """
    from accelpy._application import Application
    product_id, version_prefix = prefix.split(':', 1)
    return (
        f"{product_id}:{version}"
        for version in Application.list_versions(product_id, version_prefix))
Пример #12
0
def test_application(application_yml, tmpdir):
    """
    Test applications based on their application definition.

    Args:
        application_yml (dict): Application detail.
        tmpdir (py.path.local) tmpdir pytest fixture.
    """
    from os import environ
    from time import sleep, time
    from subprocess import run, STDOUT, PIPE
    import accelpy._host as accelpy_host
    from accelpy._host import Host
    from accelpy._application import Application

    yaml_path = application_yml['path']
    provider = application_yml['provider']

    environ['ACCELPY_DEBUG'] = 'True'

    # Mock config dir
    accelpy_host_config_dir = accelpy_host.CONFIG_DIR
    config_dir = tmpdir.join('config').ensure(dir=True)
    accelpy_host.CONFIG_DIR = str(config_dir)

    app = Application(yaml_path)

    # Tests
    try:

        with Host(application=yaml_path,
                  provider=provider,
                  name=get_name(app),
                  destroy_on_exit=True,
                  keep_config=False) as host:

            # Run Packer configuration check
            host._packer.validate()

            # Apply configuration
            host.apply()

            # Get test command
            command = app[provider]['test']['shell']
            if not command:
                pytest.xfail('No test defined')

            # Run test
            else:
                # Evaluate "accelpy" shell variables
                for attr in dir(host):

                    if attr.startswith('_'):
                        continue

                    shell_var = f'$(accelpy {attr})'
                    if shell_var in command:
                        command = command.replace(shell_var,
                                                  getattr(host, attr))

                # Run test command (With retries during 1 minute)
                print(f'\nRunning test command:\n{command.strip()}\n')
                timeout = time() + 60
                while time() < timeout:
                    result = run(command,
                                 universal_newlines=True,
                                 stderr=STDOUT,
                                 stdout=PIPE,
                                 shell=True)
                    if not result.returncode:
                        break
                    sleep(1)

                print(f'\nTest command returned: '
                      f'{result.returncode}\n{result.stdout}')

                if result.returncode:
                    pytest.fail(pytrace=False)

    # Restore mocked config dir
    finally:
        accelpy_host.CONFIG_DIR = accelpy_host_config_dir
        del environ['ACCELPY_DEBUG']
Пример #13
0
def test_apply(application_yml, tmpdir):
    """
    Test applications based on their application definition.

    Args:
        application_yml (dict): Application detail.
        tmpdir (py.path.local) tmpdir pytest fixture
    """
    from time import sleep
    from subprocess import run, STDOUT, PIPE
    import accelpy._host as accelpy_host
    from accelpy._host import Host
    from accelpy._application import Application

    yaml_path = application_yml['path']
    provider = application_yml['provider']

    # Mock config dir
    accelpy_host_config_dir = accelpy_host.CONFIG_DIR
    config_dir = tmpdir.join('config').ensure(dir=True)
    accelpy_host.CONFIG_DIR = str(config_dir)

    # Tests
    try:

        with Host(application=yaml_path,
                  provider=provider,
                  destroy_on_exit=True,
                  keep_config=False) as host:

            # Apply configuration
            host.apply()

            # Get test command
            command = Application(yaml_path).get('test', 'shell', env=provider)

            # Evaluate "accelpy" shell variables
            for attr in dir(host):

                if attr.startswith('_'):
                    continue

                shell_var = f'$(accelpy {attr})'
                if shell_var in command:
                    command = command.replace(shell_var, getattr(host, attr))

            # Run test command
            sleep(5)

            print(f'\nRunning test command:\n{command.strip()}\n')
            result = run(command,
                         universal_newlines=True,
                         stderr=STDOUT,
                         stdout=PIPE,
                         shell=True)
            print(f'\nTest command returned: '
                  f'{result.returncode}\n{result.stdout}')

            if result.returncode:
                pytest.fail(pytrace=False)

    # Restore mocked config dir
    finally:
        accelpy_host.CONFIG_DIR = accelpy_host_config_dir
Пример #14
0
def test_application():
    """
    Test common Application features.
    """
    from json import loads
    from copy import deepcopy
    import accelpy._application as accelpy_app
    from accelpy._application import Application
    from accelpy.exceptions import RuntimeException

    # Test: Load from dict
    definition = {
        'application': {
            'product_id': 'my_product_id',
            'version': '1.0.0',
            'type': 'container_service'
        },
        'package': [{
            'type': 'container_image',
            'name': 'my_container_image'
        }],
        'fpga': {
            'image': ['my_fpga_image'],
            'count': 1
        },
        'accelize_drm': {
            'use_service': False
        }
    }
    app = Application(definition)

    # Test: __getitem__
    assert app['application']['product_id'] == 'my_product_id'

    # Test: As dict
    assert app.to_dict()['application']['product_id'] == 'my_product_id'

    # Test: Cannot delete local application
    with pytest.raises(RuntimeException):
        app.delete()

    # Mock server
    accelpy_app_accelize_ws_session = accelpy_app.accelize_ws_session

    class Server:
        """Mocked server"""
        @staticmethod
        def request(path, *_, method='get', data=None, **__):
            """Mocked server response"""

            if '/productconfiguration/' in path:
                if method == 'get':
                    srv_def = deepcopy(definition)
                    srv_def['application']['configuration_id'] = 2
                    return dict(results=[srv_def])

                elif method == 'post':
                    assert loads(data) == definition, 'Pushed definition match'
                    return {'application': {'configuration_id': 2}}

                elif method == 'delete':
                    assert path.endswith('/2/')
                    return

            elif ('/productconfigurationlistversion/' in path
                  and method == 'get'):
                return dict(results=['1.0.0'])

            elif ('/productconfigurationlistproduct/' in path
                  and method == 'get'):
                return dict(results=['product'])

            raise ValueError(f'path={path}; method={method}')

    accelpy_app.accelize_ws_session = Server

    # Test basic mocked server flow
    try:
        # Test: push
        Application(definition).push()

        # Test: Get
        assert Application.from_id('app').to_dict() == definition

        # Test: List
        assert Application.list() == ['product']

        # Test: List version
        assert Application.list_versions('product') == ['1.0.0']
        assert Application.list_versions('product', '1') == ['1.0.0']

        # Test: Delete
        Application.from_id('app').delete()

    finally:
        accelpy_app.accelize_ws_session = accelpy_app_accelize_ws_session
Пример #15
0
def test_lint(tmpdir):
    """
    Test application definition file lint

    Args:
        tmpdir (py.path.local) tmpdir pytest fixture
    """
    from accelpy._application import Application, lint
    from accelpy.exceptions import ConfigurationException

    # Mock yaml definition file
    yml_file = tmpdir.join('application.yml')

    # Test: Load valid file
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image
  version: 1.0.0

  # Override of value in environment
  my_provider:
    type: vm_image
    name: my_vm_image

firewall_rules:
  - start_port: 1000
    end_port: 1000
    protocol: tcp
    direction: ingress
  - start_port: 1001
    end_port: 1100
    protocol: tcp
    direction: egress

fpga:
  # Mandatory value only in environment
  my_provider:
    image: my_fpga_image
""")
    app = Application(yml_file)

    # Test: __getitem__
    assert app['application']['name'] == 'my_app'

    # Test section _node
    assert isinstance(app['firewall_rules'], list)
    assert isinstance(app['application'], dict)

    # Test: get
    assert app.get('fpga', 'image') is None
    assert app.get('fpga', 'image', 'my_provider') == ['my_fpga_image']
    assert app.get('package', 'type') == 'container_image'
    assert app.get('package', 'type', 'my_provider') == 'vm_image'

    # Test: save
    app['package']['name'] = 'another_image'
    app.save()
    assert Application(yml_file)['package']['name'] == 'another_image'

    # Test: environment
    assert app.environments == {'my_provider'}

    # Test: Load valid file with missing not mandatory section
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image: image
""")
    app = Application(yml_file)
    assert app['firewall_rules'] == []

    # Test: Load bad section type
    yml_file.write("""
application:
  - name: my_app
    version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: Missing mandatory value in default keys
    yml_file.write("""
application:
  name: my_app

package:
  # Missing image
  type: container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: Missing mandatory value in environment keys
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  # Missing image
  my_provider:
    type: container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: Value not in list
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

firewall_rules:
  - start_port: 1000
    end_port: 1000
    protocol: tcp
    # No a known direction
    direction: no_direction

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: Bad value type
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image: image
  # Count should be an int
  count: "1"
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: Bad value type in list
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image:
    - image_slot0
    - 1
  count: 1
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)

    # Test: List of values
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image:
    - image_slot0
    - image_slot1
  count: 1
""")
    lint(yml_file)

    # Test: Auto list conversion
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image: image_slot0
  count: 1
""")
    lint(yml_file)

    # Test: top level list bad value
    yml_file.write("""
application:
  name: my_app
  version: 1.0.0

package:
  type: container_image
  name: my_container_image

fpga:
  image: 1
  count: 1
""")
    with pytest.raises(ConfigurationException):
        lint(yml_file)
Пример #16
0
def test_host(tmpdir):
    """
    Test host

    Args:
        tmpdir (py.path.local) tmpdir pytest fixture
    """
    import accelpy._common as common
    import accelpy._host as accelpy_host
    from accelpy._host import Host, iter_hosts
    from accelpy.exceptions import (ConfigurationException, AccelizeException,
                                    AuthenticationException)
    from accelpy._application import Application

    from tests.test_core_terraform import mock_terraform_provider
    from tests.test_core_packer import mock_packer_provider
    from tests.test_core_ansible import mock_ansible_local
    from tests.test_core_application import mock_application

    source_dir = tmpdir.join('source').ensure(dir=True)

    # Mock ~/.accelize
    common_home_dir = common.HOME_DIR
    common.HOME_DIR = tmpdir.join('home').ensure(dir=True)

    # Mock config dir
    accelpy_host_config_dir = accelpy_host.CONFIG_DIR
    config_dir = tmpdir.join('config').ensure(dir=True)
    accelpy_host.CONFIG_DIR = str(config_dir)

    # Mock application definition file & provider specific configuration
    application = mock_application(source_dir)
    mock_terraform_provider(source_dir)
    artifact = mock_packer_provider(source_dir)
    source_dir.ensure('cred.json')

    # Tests
    try:

        # Test: Host generation with not arguments should raise
        with pytest.raises(ConfigurationException):
            Host()

        # Test: Create host with specified name + use as context manager
        name = 'testing'
        with Host(application=application, name=name,
                  user_config=source_dir) as host:
            assert host.name == name
            assert name in str(host)
            assert name in repr(host)

        # Test: Create host with generated name
        host = Host(application=application, user_config=source_dir)
        assert host.name
        name = host.name

        # Test: Initialization
        host_config_dir = config_dir.join(name)
        assert host_config_dir.join('playbook.yml').isfile()
        assert host_config_dir.join('common.tf').isfile()
        assert host_config_dir.join('template.json').isfile()
        assert host_config_dir.join('application.yml').isfile()
        assert host._ansible
        assert host._application
        assert host._terraform
        assert host._packer

        # Test: Output values should raise as not applied
        with pytest.raises(ConfigurationException):
            assert host.private_ip

        # Test: Terraform plan
        assert host.plan()

        # Test: Output values should still raise if only planned
        with pytest.raises(ConfigurationException):
            assert host.private_ip

        # Test: Terraform apply
        host.apply(quiet=True)
        assert host_config_dir.join('terraform.tfstate').isfile()

        # Test: Output variable
        assert host.private_ip == "127.0.0.1"
        assert host.public_ip == "127.0.0.1"
        assert host.ssh_user == "user"
        assert host.ssh_private_key == str(
            host_config_dir.join('ssh_private.pem'))

        # Test: Terraform destroy
        host.destroy(quiet=True, delete=True)

        # Test: Do destroy on exit
        with Host(application=application,
                  user_config=source_dir,
                  destroy_on_exit=True,
                  keep_config=False) as host:

            assert not config_dir.join(
                f'{host.name}/terraform.tfstate').isfile()

            host.apply(quiet=True)
            assert config_dir.join(f'{host.name}/terraform.tfstate').isfile()

        assert not config_dir.join(host.name).exists()

        # Test: Do not destroy on exit
        with Host(application=application, user_config=source_dir) as host:
            host_not_destroyed = host.name
            host.apply(quiet=True)
            assert config_dir.join(f'{host.name}/terraform.tfstate').isfile()

        assert config_dir.join(f'{host.name}/terraform.tfstate').isfile()

        # Test: Do not destroy on exit, but do not keep un-applied config
        with Host(application=application,
                  user_config=source_dir,
                  keep_config=False) as host:
            assert config_dir.join(host.name).exists()

        assert not config_dir.join(host.name).exists()

        # Test: Clean up in case of initialization error
        with pytest.raises(AccelizeException):
            Host(application='path_not_exists',
                 user_config=source_dir,
                 name='should_be_cleaned')
        assert not config_dir.join('should_be_cleaned').exists()

        # Test: Load existing host
        with Host(name=host_not_destroyed) as host:
            assert host.private_ip
            assert host._application

        # Test: Iter over host
        config_dir.join('latest').ensure()
        assert host_not_destroyed in tuple(host.name for host in iter_hosts())

        # Test: Build image
        provider = 'testing'
        with Host(application=application,
                  user_config=source_dir,
                  provider=provider) as host:

            # Mock ansible playbook
            host_config_dir = config_dir.join(host.name)
            mock_ansible_local(host_config_dir)

            application_yaml = str(host_config_dir.join('application.yml'))

            # Test: Build image and with no application update
            assert Application(
                application_yaml)[provider]['package'][0]['name'] == 'my_image'

            host.build(quiet=True)

            assert Application(
                application_yaml)[provider]['package'][0]['name'] == 'my_image'

            # Test: Build image and update application
            host.build(quiet=True, update_application=True)

            assert Application(
                application_yaml)[provider]['package'][0]['name'] == artifact

        # Test: Missing Accelize DRM configuration
        application = mock_application(
            source_dir, override={'accelize_drm': {
                'use_service': True
            }})

        with pytest.raises(ConfigurationException):
            Host(application=application,
                 user_config=source_dir,
                 keep_config=False)

        # Test: Missing Accelize DRM credentials
        source_dir.join('cred.json').remove()
        application = mock_application(source_dir)
        with pytest.raises(AuthenticationException):
            Host(application=application,
                 user_config=source_dir,
                 keep_config=False)

    # Restore mocked config dir
    finally:
        accelpy_host.CONFIG_DIR = accelpy_host_config_dir
        common.HOME_DIR = common_home_dir
Пример #17
0
def test_lint(tmpdir):
    """
    Test application definition file lint

    Args:
        tmpdir (py.path.local) tmpdir pytest fixture
    """
    from accelpy._application import Application
    from accelpy.exceptions import ConfigurationException

    # Mock yaml definition file
    yml_file = tmpdir.join('application.yml')

    # Test: Load valid file
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image
    version: 1.0.0

    # Override of value in provider
    my_provider:
      type: vm_image
      name: my_vm_image

firewall_rules:
  - start_port: 1000
    end_port: 1000
    protocol: tcp
    direction: ingress
  - start_port: 1001
    end_port: 1100
    protocol: tcp
    direction: egress

fpga:
  # Mandatory value only in provider
  my_provider:
    image: my_fpga_image
""")
    app = Application(yml_file)

    # Test section _node
    assert isinstance(app['firewall_rules'], list)
    assert isinstance(app['application'], dict)

    # Test: get
    assert app['fpga']['image'] == []
    assert app['my_provider']['fpga']['image'] == ['my_fpga_image']
    assert app['package'][0]['type'] == 'container_image'
    assert app['my_provider']['package'][0]['type'] == 'vm_image'

    # Test: save and reload
    app['package'][0]['my_provider']['name'] = 'another_image'
    app.save()
    assert Application(
        yml_file)['my_provider']['package'][0]['name'] == 'another_image'

    # Test: provider
    assert app.providers == {'my_provider'}

    # Test: Load valid file with missing not mandatory section
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
""")
    app = Application(yml_file)
    assert app['firewall_rules'] == []

    # Test: Load file with inferred list section from mapping
    yml_file.write("""
    application:
      product_id: my_product_id
      version: 1.0.0

    package:
      type: container_image
      name: my_container_image

    fpga:
      image: image
    """)
    app = Application(yml_file)
    assert app['package'][0]['type'] == 'container_image'

    # Test: Provider with reserved name
    yml_file.write("""
    application:
      product_id: my_product_id
      version: 1.0.0

    package:
      - type: container_image
        name: my_container_image

    fpga:
      image: image
      application:
        image: image
    """)
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Missing or empty package section
    yml_file.write("""
    application:
      product_id: my_product_id
      version: 1.0.0

    package: []

    fpga:
      image: image
    """)
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    yml_file.write("""
    application:
      product_id: my_product_id
      version: 1.0.0

    fpga:
      image: image
    """)
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Load bad section type
    yml_file.write("""
application:
  - product_id: my_product_id
    version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Missing mandatory value in default keys
    yml_file.write("""
application:
  product_id: my_product_id

package:
  # Missing image
  - type: container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Missing mandatory value in provider keys
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  # Missing image
  - my_provider:
      type: container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Value not in list
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

firewall_rules:
  - start_port: 1000
    end_port: 1000
    protocol: tcp
    # No a known direction
    direction: no_direction

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Bad value type
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
  # Count should be an int
  count: "1"
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Bad value type for str type
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
  # Driver version should be a str
  driver_version: 1.0
    """)
    Application(yml_file)

    # Test: Bad value type in list
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image:
    - image_slot0
    - 1
  count: 1
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: List of values
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image:
    - image_slot0
    - image_slot1
  count: 1
""")
    Application(yml_file)

    # Test: Auto list conversion
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image_slot0
  count: 1
""")
    Application(yml_file)

    # Test: top level list bad value
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: 1
  count: 1
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Extra section should not raise
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
  
extra_section:
  extra_key: value
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Extra key should raise
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
  extra_key: value
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Extra key should raise in provider
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
  my_provider:
    extra_key: value
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)

    # Test: Value does not match regex
    yml_file.write("""
application:
  product_id: my_product_id
  version: 1.0.0.0.0

package:
  - type: container_image
    name: my_container_image

fpga:
  image: image
""")
    with pytest.raises(ConfigurationException):
        Application(yml_file)