##############################################################################
# Copyright (c) 2017 ZTE Corp and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
import pytest
import argparse
import os
import sys
import shutil
import yaml
import mock
sys.modules['libvirt'] = mock.Mock()

from deploy import environment  # noqa: ignore=E402
from deploy.deploy import (
    config_arg_parser,
    DaisyDeployment,
    parse_arguments,
    main
)   # noqa: ignore=E402


def test_config_arg_parser():
    parser = config_arg_parser()
    assert isinstance(parser, argparse.ArgumentParser)


@pytest.fixture(scope="session")
def conf_file_dir(data_root):
    return os.path.join(data_root, 'lab_conf')


@pytest.mark.parametrize('kwargs, check_err, expect_dasiy_info', [
    ({'lab_name': 'zte',
      'pod_name': 'virtual1',
      'src_deploy_file': 'deploy_virtual1.yml',
      'net_file': 'network_virtual1.yml',
      'bin_file': 'opnfv.bin',
      'daisy_only': False,
      'cleanup_only': False,
      'remote_dir': '/home/daisy',
      'work_dir': 'workdir',
      'storage_dir': 'vms',
      'pxe_bridge': 'pxebr',
      'deploy_log': 'deploy.log',
      'scenario': 'os-nosdn-nofeature-ha'},
     [],
     {'name': 'daisy',
      'image': 'daisy.qcow2',
      'address': '10.20.11.2',
      'gateway': '10.20.11.1',
      'password': 'r00tme',
      'disk_size': 50}),
    ({'lab_name': 'zte',
      'pod_name': 'pod1',
      'src_deploy_file': 'deploy_baremetal.yml',
      'net_file': 'network_baremetal.yml',
      'bin_file': 'opnfv.bin',
      'daisy_only': False,
      'cleanup_only': True,
      'remote_dir': '/home/daisy',
      'work_dir': 'workdir',
      'storage_dir': 'vms',
      'pxe_bridge': 'pxebr',
      'deploy_log': 'deploy.log',
      'scenario': 'os-odl-nofeature-ha'},
     ['disk_size invalid'],
     {'name': 'daisy',
      'image': 'daisy.qcow2',
      'address': '10.20.0.2',
      'gateway': '10.20.0.1',
      'password': 'r00tme',
      'disk_size': 50})])
@mock.patch('deploy.deploy.deploy_schema_validate')
@mock.patch('deploy.deploy.err_exit')
def test_create_DaisyDeployment_instance(mock_err_exit, mock_deploy_schema_validate,
                                         kwargs, expect_dasiy_info,
                                         conf_file_dir, tmpdir,
                                         check_err):
    kwargs['src_deploy_file'] = os.path.join(conf_file_dir, kwargs['src_deploy_file'])
    kwargs['net_file'] = os.path.join(conf_file_dir, kwargs['net_file'])
    tmpdir.join(kwargs['bin_file']).write('testdata')
    kwargs['bin_file'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['bin_file'])
    kwargs['deploy_log'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['deploy_log'])
    tmpsubdir = tmpdir.mkdir(kwargs['work_dir'])
    kwargs['work_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    tmpsubdir = tmpdir.mkdir(kwargs['storage_dir'])
    kwargs['storage_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    mock_deploy_schema_validate.return_value = check_err

    deploy = DaisyDeployment(**kwargs)
    assert (deploy.lab_name, deploy.pod_name, deploy.src_deploy_file, deploy.net_file, deploy.bin_file,
            deploy.daisy_only, deploy.cleanup_only, deploy.remote_dir, deploy.work_dir, deploy.storage_dir,
            deploy.deploy_log, deploy.scenario) == \
           (kwargs['lab_name'], kwargs['pod_name'], kwargs['src_deploy_file'], kwargs['net_file'],
            kwargs['bin_file'], kwargs['daisy_only'], kwargs['cleanup_only'], kwargs['remote_dir'],
            kwargs['work_dir'], kwargs['storage_dir'], kwargs['deploy_log'], kwargs['scenario'])
    if check_err:
        mock_err_exit.assert_called_once_with('Configuration deploy.yml check failed!')
    else:
        mock_err_exit.assert_not_called()

    assert deploy.deploy_file_name == 'final_deploy.yml'
    assert deploy.deploy_file == os.path.join(deploy.work_dir, 'final_deploy.yml')
    assert os.path.isfile(os.path.join(deploy.work_dir, 'final_deploy.yml'))

    if not deploy.cleanup_only:
        assert deploy.net_file_name == os.path.basename(kwargs['net_file'])
        with open(deploy.net_file) as yaml_file:
            expected_net_struct = yaml.safe_load(yaml_file)
            assert expected_net_struct == deploy.net_struct
    else:
        assert deploy.net_struct is None

    if 'virtual' in kwargs['src_deploy_file']:
        assert (deploy.adapter == 'libvirt' and deploy.pxe_bridge == 'daisy1')

    else:
        assert (deploy.adapter == 'ipmi' and deploy.pxe_bridge == 'pxebr')

    expect_dasiy_info['image'] = os.path.join(kwargs['storage_dir'], expect_dasiy_info['image'])
    assert deploy.daisy_server_info == expect_dasiy_info
    tmpdir.remove()


@pytest.mark.parametrize('kwargs, is_use_pdf, exp_deploy_file', [
    (
        {
            'lab_name': 'zte',
            'pod_name': 'virtual1',
            'src_deploy_file': 'deploy_virtual1.yml',
            'net_file': 'network_virtual1.yml',
            'bin_file': 'opnfv.bin',
            'daisy_only': False,
            'cleanup_only': False,
            'remote_dir': '/home/daisy',
            'work_dir': 'workdir',
            'storage_dir': 'vms',
            'pxe_bridge': 'libvirt',
            'deploy_log': 'deploy.log',
            'scenario': 'os-nosdn-nofeature-ha'
        }, False, 'deploy_vir1_nosdn-ha.yml'
    ),
    (
        {
            'lab_name': 'zte',
            'pod_name': 'pod1',
            'src_deploy_file': 'deploy_baremetal.yml',
            'net_file': 'network_baremetal.yml',
            'bin_file': 'opnfv.bin',
            'daisy_only': False,
            'cleanup_only': False,
            'remote_dir': '/home/daisy',
            'work_dir': 'workdir',
            'storage_dir': 'vms',
            'pxe_bridge': 'pxebr',
            'deploy_log': 'deploy.log',
            'scenario': 'os-odl-nofeature-ha'
        }, True, 'deploy_bare_odl-ha.yml'
    )])
@mock.patch.object(DaisyDeployment, '_use_pod_descriptor_file')
def test__construct_final_deploy_conf_in_DaisyDeployment(mock__use_pod_descriptor_file,
                                                         conf_file_dir, tmpdir,
                                                         kwargs, is_use_pdf, exp_deploy_file):
    kwargs['src_deploy_file'] = os.path.join(conf_file_dir, kwargs['src_deploy_file'])
    kwargs['net_file'] = os.path.join(conf_file_dir, kwargs['net_file'])
    tmpdir.join(kwargs['bin_file']).write('testdata')
    kwargs['bin_file'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['bin_file'])
    kwargs['deploy_log'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['deploy_log'])
    tmpsubdir = tmpdir.mkdir(kwargs['work_dir'])
    kwargs['work_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    tmpsubdir = tmpdir.mkdir(kwargs['storage_dir'])
    kwargs['storage_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    exp_deploy_file_path = os.path.join(conf_file_dir, exp_deploy_file)
    pdf_deploy_file = None if not is_use_pdf else kwargs['src_deploy_file']

    mock__use_pod_descriptor_file.return_value = pdf_deploy_file
    daisy_deploy = DaisyDeployment(**kwargs)
    mock__use_pod_descriptor_file.asser_called_once_with()
    with open(exp_deploy_file_path) as yaml_file:
        exp_deploy_struct = yaml.safe_load(yaml_file)
    assert daisy_deploy.deploy_struct == exp_deploy_struct
    tmpdir.remove()


@pytest.mark.parametrize('kwargs', [
    (
        {
            'lab_name': 'zte',
            'pod_name': 'virtual1',
            'src_deploy_file': 'deploy_baremetal.yml',
            'net_file': 'network_baremetal.yml',
            'bin_file': 'opnfv.bin',
            'skip_daisy': False,
            'daisy_only': False,
            'cleanup_only': False,
            'remote_dir': '/home/daisy',
            'work_dir': 'workdir',
            'storage_dir': 'vms',
            'pxe_bridge': 'pxebr',
            'deploy_log': 'deploy.log',
            'scenario': 'os-nosdn-nofeature-ha'
        }
    ),
    (
        {
            'lab_name': 'zte',
            'pod_name': 'pod1',
            'src_deploy_file': 'deploy_baremetal.yml',
            'net_file': 'network_baremetal.yml',
            'bin_file': 'opnfv.bin',
            'skip_daisy': False,
            'daisy_only': False,
            'cleanup_only': True,
            'remote_dir': '/home/daisy',
            'work_dir': 'workdir',
            'storage_dir': 'vms',
            'pxe_bridge': 'pxebr',
            'deploy_log': 'deploy.log',
            'scenario': 'os-odl-nofeature-ha'
        }
    ),
    (
        {
            'lab_name': 'zte',
            'pod_name': 'pod1',
            'src_deploy_file': 'deploy_baremetal.yml',
            'net_file': 'network_baremetal.yml',
            'bin_file': 'opnfv.bin',
            'skip_daisy': False,
            'daisy_only': True,
            'cleanup_only': False,
            'remote_dir': '/home/daisy',
            'work_dir': 'workdir',
            'storage_dir': 'vms',
            'pxe_bridge': 'pxebr',
            'deploy_log': 'deploy.log',
            'scenario': 'os-odl-nofeature-ha'
        }
    )])
@mock.patch.object(environment.BareMetalEnvironment, 'delete_old_environment')
@mock.patch.object(environment.BareMetalEnvironment, 'create_daisy_server')
@mock.patch.object(environment.BareMetalEnvironment, 'install_daisy')
@mock.patch.object(environment.BareMetalEnvironment, 'connect_daisy_server')
@mock.patch.object(environment.BareMetalEnvironment, 'deploy')
def test_run_in_DaisyDeployment(mock_deploy, mock_connect_daisy_server, mock_install_daisy,
                                mock_create_daisy_server, mock_delete_old_environment,
                                conf_file_dir, tmpdir, kwargs):
    kwargs['src_deploy_file'] = os.path.join(conf_file_dir, kwargs['src_deploy_file'])
    kwargs['net_file'] = os.path.join(conf_file_dir, kwargs['net_file'])
    tmpdir.join(kwargs['bin_file']).write('testdata')
    kwargs['bin_file'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['bin_file'])
    kwargs['deploy_log'] = os.path.join(tmpdir.dirname, tmpdir.basename, kwargs['deploy_log'])
    tmpsubdir = tmpdir.mkdir(kwargs['work_dir'])
    kwargs['work_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    tmpsubdir = tmpdir.mkdir(kwargs['storage_dir'])
    kwargs['storage_dir'] = os.path.join(tmpsubdir.dirname, tmpsubdir.basename)
    daisy_deploy = DaisyDeployment(**kwargs)
    daisy_deploy.run()
    mock_delete_old_environment.asser_called_once_with()
    if daisy_deploy.cleanup_only is False:
        mock_create_daisy_server.assert_called_once_with()
        if daisy_deploy.daisy_only is False:
            mock_deploy.assert_called_once_with(daisy_deploy.deploy_file,
                                                daisy_deploy.net_file,
                                                skip_preparation=False)
            mock_connect_daisy_server.assert_called_once_with(daisy_deploy.remote_dir,
                                                              daisy_deploy.bin_file,
                                                              daisy_deploy.deploy_file_name,
                                                              daisy_deploy.net_file_name)
            mock_install_daisy.assert_called_once_with()
        else:
            mock_deploy.assert_not_called()
    else:
        mock_create_daisy_server.assert_not_called()
    tmpdir.remove()


@pytest.mark.parametrize('cleanup_only', [
    (False), (True)])
@mock.patch('deploy.deploy.argparse.ArgumentParser.parse_args')
@mock.patch('deploy.deploy.check_sudo_privilege')
@mock.patch('deploy.deploy.save_log_to_file')
@mock.patch('deploy.deploy.check_scenario_valid')
@mock.patch('deploy.deploy.check_file_exists')
@mock.patch('deploy.deploy.make_file_executable')
@mock.patch('deploy.deploy.confirm_dir_exists')
def test_parse_arguments(mock_confirm_dir_exists, mock_make_file_executable,
                         mock_check_file_exists, mock_check_scenario_valid,
                         mock_save_log_to_file, mock_check_sudo_privilege,
                         mock_parse_args, cleanup_only, tmpdir):
    class MockArg():
        def __init__(self, labs_dir, lab_name, pod_name, bin_file, skip_daisy, daisy_only,
                     cleanup_only, remote_dir, work_dir, storage_dir, pxe_bridge,
                     deploy_log, scenario):
            self.labs_dir = labs_dir
            self.lab_name = lab_name
            self.pod_name = pod_name
            self.bin_file = bin_file
            self.skip_daisy = skip_daisy
            self.daisy_only = daisy_only
            self.cleanup_only = cleanup_only
            self.remote_dir = remote_dir
            self.work_dir = work_dir
            self.storage_dir = storage_dir
            self.pxe_bridge = pxe_bridge
            self.deploy_log = deploy_log
            self.scenario = scenario

    bin_file_path = os.path.join(tmpdir.dirname, tmpdir.basename, 'opnfv.bin')
    deploy_log_path = os.path.join(tmpdir.dirname, tmpdir.basename, 'deploy.log')
    labs_dir = '/var/tmp/securedlab'
    conf_base_dir = os.path.join(labs_dir, 'labs', 'zte', 'pod2')
    deploy_file = os.path.join(conf_base_dir, 'daisy/config/deploy.yml')
    net_file = os.path.join(conf_base_dir, 'daisy/config/network.yml')
    expected = {
        'labs_dir': labs_dir,
        'lab_name': 'zte',
        'pod_name': 'pod2',
        'src_deploy_file': deploy_file,
        'net_file': net_file,
        'bin_file': bin_file_path,
        'skip_daisy': False,
        'daisy_only': False,
        'cleanup_only': cleanup_only,
        'remote_dir': '/home/daisy',
        'work_dir': '/tmp/workdir',
        'storage_dir': '/home/qemu/vms',
        'pxe_bridge': 'pxebr',
        'deploy_log': deploy_log_path,
        'scenario': 'os-nosdn-nofeature-noha'
    }
    mockarg = MockArg('/var/tmp/securedlab', 'zte', 'pod2', bin_file_path, False, False, cleanup_only, '/home/daisy', '/tmp/workdir',
                      '/home/qemu/vms', 'pxebr', deploy_log_path, 'os-nosdn-nofeature-noha')
    mock_parse_args.return_value = mockarg
    ret = parse_arguments()
    assert ret == expected
    mock_check_sudo_privilege.assert_called_once_with()
    mock_save_log_to_file.assert_called_once_with(deploy_log_path)
    mock_check_scenario_valid.assert_called_once_with('os-nosdn-nofeature-noha')
    if cleanup_only is False:
        mock_check_file_exists.assert_called_once_with(net_file)
    mock_make_file_executable.assert_called_once_with(bin_file_path)
    assert mock_confirm_dir_exists.call_count == 2
    tmpdir.remove()


@pytest.mark.parametrize('cleanup_only', [
    (False), (True)])
@mock.patch.object(DaisyDeployment, 'run')
@mock.patch('deploy.deploy.parse_arguments')
def test_main(mock_parse_arguments, mock_run, cleanup_only, tmpdir, conf_file_dir):
    bin_file_path = os.path.join(tmpdir.dirname, tmpdir.basename, 'opnfv.bin')
    with open(bin_file_path, 'w') as f:
        f.write('test_data')
    deploy_log_path = os.path.join(tmpdir.dirname, tmpdir.basename, 'deploy.log')
    deploy_file = os.path.join(tmpdir.dirname, tmpdir.basename, 'deploy.yml')
    src_deploy_file = os.path.join(conf_file_dir, 'deploy_baremetal.yml')
    shutil.copyfile(src_deploy_file, deploy_file)
    net_file = os.path.join(tmpdir.dirname, tmpdir.basename, 'network.yml')
    src_network_file = os.path.join(conf_file_dir, 'network_baremetal.yml')
    shutil.copyfile(src_network_file, net_file)
    work_dir = os.path.join(tmpdir.dirname, tmpdir.basename)
    kwargs = {
        'lab_name': 'zte',
        'pod_name': 'pod2',
        'src_deploy_file': deploy_file,
        'net_file': net_file,
        'bin_file': bin_file_path,
        'daisy_only': False,
        'cleanup_only': cleanup_only,
        'remote_dir': '/home/daisy',
        'work_dir': work_dir,
        'storage_dir': '/home/qemu/vms',
        'pxe_bridge': 'pxebr',
        'deploy_log': deploy_log_path,
        'scenario': 'os-nosdn-nofeature-noha'
    }
    mock_parse_arguments.return_value = kwargs
    main()
    mock_parse_arguments.assert_called_once_with()
    mock_run.assert_called_once_with()
    tmpdir.remove()