<filename>cli/tests/unit/test_task.py
import pytest
from mock import MagicMock, patch

from dcos import mesos
from dcos.errors import DCOSException
from dcoscli.log import log_files
from dcoscli.task.main import _dcos_log, _metrics, main

from .common import assert_mock


@patch('dcos.config.get_config')
def test_log_master_unavailable(config_mock):
    config_mock.return_value = {'core.dcos_url': 'foo'}

    """ Test master's state.json being unavailable """
    client = mesos.DCOSClient()
    client.get_master_state = _mock_exception()

    with patch('dcos.mesos.DCOSClient', return_value=client):
        args = ['task', 'log', '_']
        assert_mock(main, args, returncode=1, stderr=(b"exception\n"))


def test_log_no_tasks():
    """ Test slave's state.json being unavailable """
    with patch('dcos.mesos.DCOSClient.get_master_state', return_value={}), \
            patch('dcos.mesos.Master.tasks', return_value={}):

        stderr = b"""No matching tasks. Exiting.\n"""
        args = ['task', 'log', 'test-app-1']
        assert_mock(main, args, returncode=1, stderr=stderr)


def test_task_exact_match():
    """Test a task gets returned if it is an exact match, even if
    it is a substring of another task.
    """
    with patch('dcos.mesos.Master.slaves',
               return_value=[{"id": "foo"}, {"id": "foobar"}]):
        master = mesos.Master(None)
        assert master.slave("foo") == {"id": "foo"}


def test_log_file_unavailable():
    """ Test a file's read.json being unavailable """
    files = [mesos.MesosFile('bogus')]
    files[0].read = _mock_exception('exception')

    with pytest.raises(DCOSException) as e:
        log_files(files, True, 10)

    msg = "No files exist. Exiting."
    assert e.exconly().split(':', 1)[1].strip() == msg


def _mock_exception(contents='exception'):
    return MagicMock(side_effect=DCOSException(contents))


@patch('dcos.http.get')
@patch('dcos.config.get_config_val')
def test_dcos_log(mocked_get_config_val, mocked_http_get):
    mocked_get_config_val.return_value = 'http://127.0.0.1'

    m = MagicMock()
    m.status_code = 200
    mocked_http_get.return_value = m

    executor_info = {
        'tasks': [
            {
                'container': 'container1',
                'state': 'TASK_RUNNING',
                'statuses': [
                    {
                        'state': 'TASK_RUNNING',
                        'container_status': {
                            'container_id': {
                                'value': 'child-123',
                                'parent': {
                                    'value': 'parent-456'
                                }
                            }
                        }
                    }
                ],
                'slave_id': 'slave-123',
                'framework_id': 'framework-123',
                'id': 'id-123'
            }
        ]
    }

    task = MagicMock
    task.executor = lambda: executor_info
    _dcos_log(False, [task], 10, 'stdout', False)
    mocked_http_get.assert_called_with(
        'http://127.0.0.1/system/v1/agent/slave-123/logs/v1/range/framework/'
        'framework-123/executor/id-123/container/parent-456.child-123'
        '?skip_prev=10&filter=STREAM:STDOUT',
        headers={'Accept': 'text/plain'})


@patch('dcoscli.log.follow_logs')
@patch('dcos.http.get')
@patch('dcos.config.get_config_val')
def test_dcos_log_stream(mocked_get_config_val, mocked_http_get,
                         mocked_follow_logs):
    mocked_get_config_val.return_value = 'http://1172.16.58.3'

    m = MagicMock()
    m.status_code = 200
    mocked_http_get.return_value = m

    executor_info = {
        'tasks': [
            {
                'container': 'container1',
                'state': 'TASK_RUNNING',
                'statuses': [
                    {
                        'state': 'TASK_RUNNING',
                        'container_status': {
                            'container_id': {
                                'value': 'child-123',
                            }
                        }
                    }
                ],
                'slave_id': 'slave-123',
                'framework_id': 'framework-123',
                'id': 'id-123'
            }
        ]
    }

    task = MagicMock
    task.executor = lambda: executor_info
    _dcos_log(True, [task], 10, 'stderr', False)
    mocked_follow_logs.assert_called_with(
        'http://127.0.0.1/system/v1/agent/slave-123/logs/v1/'
        'stream/framework/framework-123/executor/id-123/container/'
        'child-123?skip_prev=10&filter=STREAM:STDERR')


@patch('dcos.http.get')
@patch('dcos.mesos.get_master')
@patch('dcos.config.get_config_val')
def test_dcos_task_metrics_agent_details(mocked_get_config_val,
                                         mocked_get_master,
                                         mocked_http_get):
    mocked_get_config_val.return_value = 'http://127.0.0.1'

    mock_http_response = MagicMock()
    mock_http_response.status_code = 200
    mocked_http_get.return_value = mock_http_response

    mock_master = MagicMock()
    mock_master.task = lambda _: {'slave_id': 'slave_id'}
    mock_master.get_container_id = lambda _: {
        'parent': {},
        'value': 'container_id'
    }
    mocked_get_master.return_value = mock_master

    _metrics(True, 'task_id', False)

    # Metrics should query both endpoints
    mocked_http_get.assert_any_call('http://127.0.0.1/system/v1/agent/'
                                    'slave_id/metrics/v0/containers/'
                                    'container_id/app')
    mocked_http_get.assert_any_call('http://127.0.0.1/system/v1/agent/'
                                    'slave_id/metrics/v0/containers/'
                                    'container_id')


@patch('dcos.http.get')
@patch('dcos.mesos.get_master')
@patch('dcos.config.get_config_val')
def test_dcos_task_metrics_agent_missing_slave(mocked_get_config_val,
                                               mocked_get_master,
                                               mocked_http_get):
    mocked_get_config_val.return_value = 'http://127.0.0.1'

    mock_http_response = MagicMock()
    mock_http_response.status_code = 200
    mocked_http_get.return_value = mock_http_response

    mock_master = MagicMock()
    mock_master.task = lambda _: {}
    mocked_get_master.return_value = mock_master

    # Should a task not have a slave ID, expect an error
    with pytest.raises(DCOSException):
        _metrics(True, 'task_id', False)
