log: use endpoint to determine logs source (#912)
DC/OS metadata endpoint will expose a config where the user is able to specify what logging strategy to use. We should be using `dcos-log` API only if user explicitly disables sandbox logging. if user enabled journald, we can use it to read system logs. https://github.com/dcos/dcos/pull/1259
This commit is contained in:
		@@ -6,10 +6,14 @@ import sys
 | 
				
			|||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import six
 | 
					import six
 | 
				
			||||||
 | 
					from six.moves import urllib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from dcos import emitting, http, packagemanager, sse, util
 | 
					from dcos import config, emitting, http, packagemanager, sse, util
 | 
				
			||||||
from dcos.cosmos import get_cosmos_url
 | 
					from dcos.cosmos import get_cosmos_url
 | 
				
			||||||
from dcos.errors import DCOSException, DefaultError
 | 
					from dcos.errors import (DCOSAuthenticationException,
 | 
				
			||||||
 | 
					                         DCOSAuthorizationException,
 | 
				
			||||||
 | 
					                         DCOSException,
 | 
				
			||||||
 | 
					                         DefaultError)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = util.get_logger(__name__)
 | 
					logger = util.get_logger(__name__)
 | 
				
			||||||
emitter = emitting.FlatEmitter()
 | 
					emitter = emitting.FlatEmitter()
 | 
				
			||||||
@@ -214,9 +218,48 @@ def dcos_log_enabled():
 | 
				
			|||||||
    :return: does cosmos have LOGGING capability.
 | 
					    :return: does cosmos have LOGGING capability.
 | 
				
			||||||
    :rtype: bool
 | 
					    :rtype: bool
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return packagemanager.PackageManager(
 | 
					
 | 
				
			||||||
 | 
					    # https://github.com/dcos/dcos/blob/master/gen/calc.py#L151
 | 
				
			||||||
 | 
					    return logging_strategy() == 'journald'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def logging_strategy():
 | 
				
			||||||
 | 
					    """ function returns logging strategy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :return: does cosmos have LOGGING capability.
 | 
				
			||||||
 | 
					    :rtype: str
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # default strategy is sandbox logging.
 | 
				
			||||||
 | 
					    strategy = 'logrotate'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    has_capability = packagemanager.PackageManager(
 | 
				
			||||||
        get_cosmos_url()).has_capability('LOGGING')
 | 
					        get_cosmos_url()).has_capability('LOGGING')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not has_capability:
 | 
				
			||||||
 | 
					        return strategy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    base_url = config.get_config_val("core.dcos_url")
 | 
				
			||||||
 | 
					    url = urllib.parse.urljoin(base_url, '/dcos-metadata/ui-config.json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not base_url:
 | 
				
			||||||
 | 
					        raise config.missing_config_exception(['core.dcos_url'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        response = http.get(url).json()
 | 
				
			||||||
 | 
					    except (DCOSAuthenticationException, DCOSAuthorizationException):
 | 
				
			||||||
 | 
					        raise
 | 
				
			||||||
 | 
					    except DCOSException:
 | 
				
			||||||
 | 
					        emitter.publish('Unable to determine logging mechanism for '
 | 
				
			||||||
 | 
					                        'your cluster. Defaulting to files API.')
 | 
				
			||||||
 | 
					        return strategy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        strategy = response['uiConfiguration']['plugins']['mesos']['logging-strategy']  # noqa: ignore=F403,E501
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return strategy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def follow_logs(url):
 | 
					def follow_logs(url):
 | 
				
			||||||
    """ Function will use dcos.sse.get to subscribe to server sent events
 | 
					    """ Function will use dcos.sse.get to subscribe to server sent events
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,7 @@ import dcoscli
 | 
				
			|||||||
from dcos import (cmds, config, emitting, errors,
 | 
					from dcos import (cmds, config, emitting, errors,
 | 
				
			||||||
                  http, mesos, packagemanager, subprocess, util)
 | 
					                  http, mesos, packagemanager, subprocess, util)
 | 
				
			||||||
from dcos.cosmos import get_cosmos_url
 | 
					from dcos.cosmos import get_cosmos_url
 | 
				
			||||||
from dcos.errors import (DCOSAuthenticationException,
 | 
					from dcos.errors import DCOSException, DefaultError
 | 
				
			||||||
                         DCOSAuthorizationException,
 | 
					 | 
				
			||||||
                         DCOSException, DefaultError)
 | 
					 | 
				
			||||||
from dcoscli import log, tables
 | 
					from dcoscli import log, tables
 | 
				
			||||||
from dcoscli.package.main import confirm
 | 
					from dcoscli.package.main import confirm
 | 
				
			||||||
from dcoscli.subcommand import default_command_info, default_doc
 | 
					from dcoscli.subcommand import default_command_info, default_doc
 | 
				
			||||||
@@ -492,32 +490,33 @@ def _log(follow, lines, leader, slave, component, filters):
 | 
				
			|||||||
    :rtype: int
 | 
					    :rtype: int
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not (leader or slave) or (leader and slave):
 | 
					    if not (leader or slave):
 | 
				
			||||||
        raise DCOSException(
 | 
					        raise DCOSException('You must choose one of --leader or --mesos-id.')
 | 
				
			||||||
            'You must choose one of --leader or --mesos-id.')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if lines is None:
 | 
					    if lines is None:
 | 
				
			||||||
        lines = 10
 | 
					        lines = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines = util.parse_int(lines)
 | 
					    lines = util.parse_int(lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    # if journald logging is disabled. Read from files API and exit.
 | 
				
			||||||
        _dcos_log(follow, lines, leader, slave, component, filters)
 | 
					    # https://github.com/dcos/dcos/blob/master/gen/calc.py#L151
 | 
				
			||||||
 | 
					    if 'journald' not in log.logging_strategy():
 | 
				
			||||||
 | 
					        if component or filters:
 | 
				
			||||||
 | 
					            raise DCOSException('--component or --filter is not '
 | 
				
			||||||
 | 
					                                'supported by files API')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fail back to mesos files API.
 | 
				
			||||||
 | 
					        mesos_files = _mesos_files(leader, slave)
 | 
				
			||||||
 | 
					        log.log_files(mesos_files, follow, lines)
 | 
				
			||||||
        return 0
 | 
					        return 0
 | 
				
			||||||
    except (DCOSAuthenticationException,
 | 
					 | 
				
			||||||
            DCOSAuthorizationException):
 | 
					 | 
				
			||||||
            raise
 | 
					 | 
				
			||||||
    except DCOSException as e:
 | 
					 | 
				
			||||||
        emitter.publish(DefaultError(e))
 | 
					 | 
				
			||||||
        emitter.publish(DefaultError('Falling back to files API...'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if component or filters:
 | 
					    # dcos-log does not support logs from leader and agent.
 | 
				
			||||||
        raise DCOSException('--component or --filter is not '
 | 
					    if leader and slave:
 | 
				
			||||||
                            'supported by files API')
 | 
					        raise DCOSException(
 | 
				
			||||||
 | 
					            'You must choose one of --leader or --mesos-id.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # fail back to mesos files API.
 | 
					    # if journald logging enabled.
 | 
				
			||||||
    mesos_files = _mesos_files(leader, slave)
 | 
					    _dcos_log(follow, lines, leader, slave, component, filters)
 | 
				
			||||||
    log.log_files(mesos_files, follow, lines)
 | 
					 | 
				
			||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -678,8 +677,6 @@ def _dcos_log(follow, lines, leader, slave, component, filters):
 | 
				
			|||||||
    :param filters: a list of filters ["key:value", ...]
 | 
					    :param filters: a list of filters ["key:value", ...]
 | 
				
			||||||
    :type filters: list
 | 
					    :type filters: list
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if not log.dcos_log_enabled():
 | 
					 | 
				
			||||||
        raise DCOSException('dcos-log is not supported')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filter_query = ''
 | 
					    filter_query = ''
 | 
				
			||||||
    if component:
 | 
					    if component:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,7 @@ import six
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import dcoscli
 | 
					import dcoscli
 | 
				
			||||||
from dcos import cmds, emitting, marathon, mesos, subprocess, util
 | 
					from dcos import cmds, emitting, marathon, mesos, subprocess, util
 | 
				
			||||||
from dcos.errors import (DCOSAuthenticationException,
 | 
					from dcos.errors import DCOSException, DefaultError
 | 
				
			||||||
                         DCOSAuthorizationException,
 | 
					 | 
				
			||||||
                         DCOSException,
 | 
					 | 
				
			||||||
                         DefaultError)
 | 
					 | 
				
			||||||
from dcoscli import log, tables
 | 
					from dcoscli import log, tables
 | 
				
			||||||
from dcoscli.subcommand import default_command_info, default_doc
 | 
					from dcoscli.subcommand import default_command_info, default_doc
 | 
				
			||||||
from dcoscli.util import decorate_docopt_usage
 | 
					from dcoscli.util import decorate_docopt_usage
 | 
				
			||||||
@@ -175,22 +172,17 @@ def _log_service(follow, lines, service, file_):
 | 
				
			|||||||
        file_ = 'stdout'
 | 
					        file_ = 'stdout'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    task = _get_service_task(service)
 | 
					    task = _get_service_task(service)
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        if 'id' not in task:
 | 
					 | 
				
			||||||
            raise DCOSException('Missing `id` in task. {}'.format(task))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        task_id = task['id']
 | 
					    # if journald logging is disabled, read from files API.
 | 
				
			||||||
        task_main._log(follow, False, lines, task_id, file_)
 | 
					    if not log.dcos_log_enabled():
 | 
				
			||||||
        return 0
 | 
					        return _log_task(task['id'], follow, lines, file_)
 | 
				
			||||||
    except (DCOSAuthenticationException,
 | 
					 | 
				
			||||||
            DCOSAuthorizationException):
 | 
					 | 
				
			||||||
        raise
 | 
					 | 
				
			||||||
    except DCOSException as e:
 | 
					 | 
				
			||||||
        emitter.publish(DefaultError(e))
 | 
					 | 
				
			||||||
        emitter.publish(DefaultError('Falling back to files API...'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    task = _get_service_task(service)
 | 
					    if 'id' not in task:
 | 
				
			||||||
    return _log_task(task['id'], follow, lines, file_)
 | 
					        raise DCOSException('Missing `id` in task. {}'.format(task))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    task_id = task['id']
 | 
				
			||||||
 | 
					    task_main._log(follow, False, lines, task_id, file_)
 | 
				
			||||||
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _log_task(task_id, follow, lines, file_):
 | 
					def _log_task(task_id, follow, lines, file_):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,7 @@ import six
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import dcoscli
 | 
					import dcoscli
 | 
				
			||||||
from dcos import cmds, config, emitting, mesos, util
 | 
					from dcos import cmds, config, emitting, mesos, util
 | 
				
			||||||
from dcos.errors import (DCOSAuthenticationException,
 | 
					from dcos.errors import DCOSException, DCOSHTTPException, DefaultError
 | 
				
			||||||
                         DCOSAuthorizationException,
 | 
					 | 
				
			||||||
                         DCOSException, DCOSHTTPException, DefaultError)
 | 
					 | 
				
			||||||
from dcoscli import log, tables
 | 
					from dcoscli import log, tables
 | 
				
			||||||
from dcoscli.subcommand import default_command_info, default_doc
 | 
					from dcoscli.subcommand import default_command_info, default_doc
 | 
				
			||||||
from dcoscli.util import decorate_docopt_usage
 | 
					from dcoscli.util import decorate_docopt_usage
 | 
				
			||||||
@@ -232,28 +230,27 @@ def _log(follow, completed, lines, task, file_):
 | 
				
			|||||||
                raise DCOSException(msg)
 | 
					                raise DCOSException(msg)
 | 
				
			||||||
        raise DCOSException('No matching tasks. Exiting.')
 | 
					        raise DCOSException('No matching tasks. Exiting.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if file_ in ('stdout', 'stderr') and log.dcos_log_enabled():
 | 
					    # if journald logging is disabled, read files API and exit.
 | 
				
			||||||
        try:
 | 
					    if not log.dcos_log_enabled():
 | 
				
			||||||
            _dcos_log(follow, tasks, lines, file_, completed)
 | 
					        mesos_files = _mesos_files(tasks, file_, client)
 | 
				
			||||||
            return 0
 | 
					        if not mesos_files:
 | 
				
			||||||
        except (DCOSAuthenticationException,
 | 
					            if fltr is None:
 | 
				
			||||||
                DCOSAuthorizationException):
 | 
					                msg = "No tasks found. Exiting."
 | 
				
			||||||
            raise
 | 
					            else:
 | 
				
			||||||
        except DCOSException as e:
 | 
					                msg = "No matching tasks. Exiting."
 | 
				
			||||||
            emitter.publish(DefaultError(e))
 | 
					            raise DCOSException(msg)
 | 
				
			||||||
            emitter.publish(DefaultError('Falling back to files API...'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mesos_files = _mesos_files(tasks, file_, client)
 | 
					        log.log_files(mesos_files, follow, lines)
 | 
				
			||||||
    if not mesos_files:
 | 
					        return 0
 | 
				
			||||||
        if fltr is None:
 | 
					 | 
				
			||||||
            msg = "No tasks found. Exiting."
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            msg = "No matching tasks. Exiting."
 | 
					 | 
				
			||||||
        raise DCOSException(msg)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log.log_files(mesos_files, follow, lines)
 | 
					    # otherwise
 | 
				
			||||||
 | 
					    if file_ in ('stdout', 'stderr'):
 | 
				
			||||||
 | 
					        _dcos_log(follow, tasks, lines, file_, completed)
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return 0
 | 
					    raise DCOSException('Invalid file {}. dcos-log only '
 | 
				
			||||||
 | 
					                        'supports stdout/stderr'.format(file_))
 | 
				
			||||||
 | 
					    return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_nested_container_id(task):
 | 
					def get_nested_container_id(task):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,8 +68,7 @@ def test_node_log_missing_slave():
 | 
				
			|||||||
    assert returncode == 1
 | 
					    assert returncode == 1
 | 
				
			||||||
    assert stdout == b''
 | 
					    assert stdout == b''
 | 
				
			||||||
    stderr_str = str(stderr)
 | 
					    stderr_str = str(stderr)
 | 
				
			||||||
    assert 'HTTP 404' in stderr_str
 | 
					    assert 'HTTP 404: Not Found' in stderr_str
 | 
				
			||||||
    assert 'No slave found with ID "bogus".' in stderr_str
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_node_log_lines():
 | 
					def test_node_log_lines():
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -188,7 +188,7 @@ def test_log_two_tasks():
 | 
				
			|||||||
    assert stderr == b''
 | 
					    assert stderr == b''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lines = stdout.decode('utf-8').split('\n')
 | 
					    lines = stdout.decode('utf-8').split('\n')
 | 
				
			||||||
    assert len(lines) == 11
 | 
					    assert len(lines) == 23
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.skipif(sys.platform == 'win32',
 | 
					@pytest.mark.skipif(sys.platform == 'win32',
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user