"""
Build ceph packages

Unit tests:

py.test -v -s tests/test_buildpackages.py

Integration tests:

teuthology-openstack --verbose --key-name myself --key-filename ~/Downloads/myself --ceph infernalis --suite teuthology/buildpackages

"""
import copy
import logging
import os
import types
from teuthology import packaging
from teuthology import misc
from teuthology.config import config as teuth_config
from teuthology.openstack import OpenStack

log = logging.getLogger(__name__)

class LocalGitbuilderProject(packaging.GitbuilderProject):

    def __init__(self):
        pass


def get_pkg_type(os_type):
    if os_type in ('centos', 'fedora', 'opensuse', 'rhel', 'sle'):
        return 'rpm'
    else:
        return 'deb'

def apply_overrides(ctx, config):
    if config is None:
        config = {}
    else:
        config = copy.deepcopy(config)

    assert isinstance(config, dict), \
        "task install only supports a dictionary for configuration"

    project, = config.get('project', 'ceph'),
    log.debug('project %s' % project)
    overrides = ctx.config.get('overrides')
    if overrides:
        install_overrides = overrides.get('install', {})
        misc.deep_merge(config, install_overrides.get(project, {}))
    return config

def get_config_install(ctx, config):
    config = apply_overrides(ctx, config)
    log.debug('install config %s' % config)
    return [(config.get('flavor', 'default'),
             config.get('tag', ''),
             config.get('branch', ''),
             config.get('sha1'))]

def get_config_install_upgrade(ctx, config):
    log.debug('install.upgrade config before override %s' % config)
    configs = []
    for (role, role_config) in config.items():
        if role_config is None:
            role_config = {}
        o = apply_overrides(ctx, role_config)

        log.debug('install.upgrade config ' + str(role_config) +
                  ' and with overrides ' + str(o))
        # for install.upgrade overrides are actually defaults
        configs.append((o.get('flavor', 'default'),
                        role_config.get('tag', o.get('tag', '')),
                        role_config.get('branch', o.get('branch', '')),
                        role_config.get('sha1', o.get('sha1'))))
    return configs

GET_CONFIG_FUNCTIONS = {
    'install': get_config_install,
    'install.upgrade': get_config_install_upgrade,
}

def lookup_configs(ctx, node):
    configs = []
    if type(node) is types.ListType:
        for leaf in node:
            configs.extend(lookup_configs(ctx, leaf))
    elif type(node) is types.DictType:
        for (key, value) in node.items():
            if key in ('install', 'install.upgrade'):
                configs.extend(GET_CONFIG_FUNCTIONS[key](ctx, value))
            elif key in ('overrides',):
                pass
            else:
                configs.extend(lookup_configs(ctx, value))
    return configs

def get_sha1(ref):
    url = teuth_config.get_ceph_git_url()
    ls_remote = misc.sh("git ls-remote " + url + " " + ref)
    return ls_remote.split()[0]

def task(ctx, config):
    """
    Build Ceph packages. This task will automagically be run
    before the task that need to install packages (this is taken
    care of by the internal teuthology task).

    The config should be as follows:

    buildpackages:
      good_machine:
        disk: 40 # GB
        ram: 48000 # MB
        cpus: 16
      min_machine:
        disk: 40 # GB
        ram: 8000 # MB
        cpus: 1

    example:

    tasks:
    - buildpackages:
        good_machine:
          disk: 40 # GB
          ram: 15000 # MB
          cpus: 16
        min_machine:
          disk: 40 # GB
          ram: 8000 # MB
          cpus: 1
    - install:

    When a buildpackages task is already included, the values it contains can be
    overriden with:

    overrides:
      buildpackages:
        good_machine:
          disk: 20 # GB
          ram: 2000 # MB
          cpus: 2
        min_machine:
          disk: 10 # GB
          ram: 1000 # MB
          cpus: 1

    """
    log.info('Beginning buildpackages...')
    if config is None:
        config = {}
    assert isinstance(config, dict), \
        'task only accepts a dict for config not ' + str(config)
    overrides = ctx.config.get('overrides', {})
    misc.deep_merge(config, overrides.get('buildpackages', {}))
    d = os.path.join(os.path.dirname(__file__), 'buildpackages')
    os_type = misc.get_distro(ctx)
    os_version = misc.get_distro_version(ctx)
    arch = ctx.config.get('arch', OpenStack().get_default_arch())
    dist = LocalGitbuilderProject()._get_distro(distro=os_type,
                                                version=os_version)
    pkg_type = get_pkg_type(os_type)
    misc.sh(
        "flock --close /tmp/buildpackages " +
        "make -C " + d + " " + os.environ['HOME'] + "/.ssh_agent")
    for (flavor, tag, branch, sha1) in lookup_configs(ctx, ctx.config):
        if tag:
            sha1 = get_sha1(tag)
        elif branch:
            sha1 = get_sha1(branch)
        log.info("building flavor = " + flavor + "," +
                 " tag = " + tag + "," +
                 " branch = " + branch + "," +
                 " sha1 = " + sha1)
        self_name = 'teuthology'
        key_name = 'teuthology'
        pkg_repo = 'packages-repository'
        security_group = 'teuthology'
        if teuth_config.openstack.has_key('selfname'):
            self_name = teuth_config.openstack['selfname']
        if teuth_config.openstack.has_key('keypair'):
            key_name = teuth_config.openstack['keypair']
        if teuth_config.openstack.has_key('package_repo'):
            pkg_repo = teuth_config.openstack['package_repo']
        if teuth_config.openstack.has_key('server_group'):
            security_group = teuth_config.openstack['server_group']
        target = (self_name + '-ceph-' +
                  pkg_type + '-' +
                  dist + '-' +
                  arch + '-' +
                  flavor + '-' +
                  sha1)
        openstack = OpenStack()
        openstack.set_provider()
        network = openstack.net()
        if network != "":
            network = " OPENSTACK_NETWORK='" + network + "' "
        openstack.image(os_type, os_version, arch) # create if it does not exist
        build_flavor = openstack.flavor_range(
            config['min_machine'], config['good_machine'], arch)
        default_arch = openstack.get_default_arch()
        http_flavor = openstack.flavor({
            'disk': 30, # GB
            'ram': 1024, # MB
            'cpus': 1,
        }, default_arch)

        lock = "/tmp/buildpackages-" + sha1 + "-" + os_type + "-" + os_version
        cmd = (". " + os.environ['HOME'] + "/.ssh_agent ; " +
               " flock --close " + lock +
               " make -C " + d +
               network +
               " SELFNAME=" + self_name +
               " KEY_NAME=" + key_name +
               " PKG_REPO=" + pkg_repo +
               " SEC_GROUP=" + security_group +
               " CEPH_GIT_URL=" + teuth_config.get_ceph_git_url() +
               " CEPH_PKG_TYPE=" + pkg_type +
               " CEPH_OS_TYPE=" + os_type +
               " CEPH_OS_VERSION=" + os_version +
               " CEPH_DIST=" + dist +
               " CEPH_ARCH=" + arch +
               " CEPH_SHA1=" + sha1 +
               " CEPH_TAG=" + tag +
               " CEPH_BRANCH=" + branch +
               " CEPH_FLAVOR=" + flavor +
               " BUILD_FLAVOR=" + build_flavor +
               " HTTP_FLAVOR=" + http_flavor +
               " HTTP_ARCH=" + default_arch +
               " BUILDPACKAGES_CANONICAL_TAGS=" + 
               ("true" if teuth_config.canonical_tags else "false") +
               " " + target +
               " ")
        log.info("Executing the following make command to build {} packages. " \
                 "Note that some values in the command, like CEPH_GIT_URL " \
                 "and BUILDPACKAGES_CANONICAL_TAGS, may differ from similar " \
                 "command-line parameter values. This is because " \
                 "the values used by this task are taken from the teuthology " \
                 "configuration file. If in doubt, tear down your teuthology " \
                 "instance and start again from scratch.".format(pkg_type))
        log.info("buildpackages make command: " + cmd)
        misc.sh(cmd)
    teuth_config.gitbuilder_host = openstack.get_ip(pkg_repo, '')
    log.info('Finished buildpackages')
