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,25 +490,17 @@ 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
|
||||||
return 0
|
if 'journald' not in log.logging_strategy():
|
||||||
except (DCOSAuthenticationException,
|
|
||||||
DCOSAuthorizationException):
|
|
||||||
raise
|
|
||||||
except DCOSException as e:
|
|
||||||
emitter.publish(DefaultError(e))
|
|
||||||
emitter.publish(DefaultError('Falling back to files API...'))
|
|
||||||
|
|
||||||
if component or filters:
|
if component or filters:
|
||||||
raise DCOSException('--component or --filter is not '
|
raise DCOSException('--component or --filter is not '
|
||||||
'supported by files API')
|
'supported by files API')
|
||||||
@@ -520,6 +510,15 @@ def _log(follow, lines, leader, slave, component, filters):
|
|||||||
log.log_files(mesos_files, follow, lines)
|
log.log_files(mesos_files, follow, lines)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# dcos-log does not support logs from leader and agent.
|
||||||
|
if leader and slave:
|
||||||
|
raise DCOSException(
|
||||||
|
'You must choose one of --leader or --mesos-id.')
|
||||||
|
|
||||||
|
# if journald logging enabled.
|
||||||
|
_dcos_log(follow, lines, leader, slave, component, filters)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def _get_slave_ip(slave):
|
def _get_slave_ip(slave):
|
||||||
""" Get an agent IP address based on mesos id.
|
""" Get an agent IP address based on mesos id.
|
||||||
@@ -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 journald logging is disabled, read from files API.
|
||||||
|
if not log.dcos_log_enabled():
|
||||||
|
return _log_task(task['id'], follow, lines, file_)
|
||||||
|
|
||||||
if 'id' not in task:
|
if 'id' not in task:
|
||||||
raise DCOSException('Missing `id` in task. {}'.format(task))
|
raise DCOSException('Missing `id` in task. {}'.format(task))
|
||||||
|
|
||||||
task_id = task['id']
|
task_id = task['id']
|
||||||
task_main._log(follow, False, lines, task_id, file_)
|
task_main._log(follow, False, lines, task_id, file_)
|
||||||
return 0
|
return 0
|
||||||
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)
|
|
||||||
return _log_task(task['id'], follow, lines, file_)
|
|
||||||
|
|
||||||
|
|
||||||
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,17 +230,8 @@ 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)
|
|
||||||
return 0
|
|
||||||
except (DCOSAuthenticationException,
|
|
||||||
DCOSAuthorizationException):
|
|
||||||
raise
|
|
||||||
except DCOSException as e:
|
|
||||||
emitter.publish(DefaultError(e))
|
|
||||||
emitter.publish(DefaultError('Falling back to files API...'))
|
|
||||||
|
|
||||||
mesos_files = _mesos_files(tasks, file_, client)
|
mesos_files = _mesos_files(tasks, file_, client)
|
||||||
if not mesos_files:
|
if not mesos_files:
|
||||||
if fltr is None:
|
if fltr is None:
|
||||||
@@ -252,9 +241,17 @@ def _log(follow, completed, lines, task, file_):
|
|||||||
raise DCOSException(msg)
|
raise DCOSException(msg)
|
||||||
|
|
||||||
log.log_files(mesos_files, follow, lines)
|
log.log_files(mesos_files, follow, lines)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# otherwise
|
||||||
|
if file_ in ('stdout', 'stderr'):
|
||||||
|
_dcos_log(follow, tasks, lines, file_, completed)
|
||||||
|
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):
|
||||||
""" Get the nested container id from mesos state.
|
""" Get the nested container id from mesos state.
|
||||||
|
|||||||
@@ -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