409 lines
11 KiB
Python
409 lines
11 KiB
Python
import copy
|
|
import datetime
|
|
import posixpath
|
|
from collections import OrderedDict
|
|
|
|
import prettytable
|
|
from dcos import mesos, util
|
|
|
|
EMPTY_ENTRY = '---'
|
|
|
|
DEPLOYMENT_DISPLAY = {'ResolveArtifacts': 'artifacts',
|
|
'ScaleApplication': 'scale',
|
|
'StartApplication': 'start',
|
|
'StopApplication': 'stop',
|
|
'RestartApplication': 'restart',
|
|
'KillAllOldTasksOf': 'kill-tasks'}
|
|
|
|
logger = util.get_logger(__name__)
|
|
|
|
|
|
def task_table(tasks):
|
|
"""Returns a PrettyTable representation of the provided mesos tasks.
|
|
|
|
:param tasks: tasks to render
|
|
:type tasks: [Task]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
("NAME", lambda t: t["name"]),
|
|
("HOST", lambda t: t.slave()["hostname"]),
|
|
("USER", lambda t: t.user()),
|
|
("STATE", lambda t: t["state"].split("_")[-1][0]),
|
|
("ID", lambda t: t["id"]),
|
|
])
|
|
|
|
tb = table(fields, tasks, sortby="NAME")
|
|
tb.align["NAME"] = "l"
|
|
tb.align["HOST"] = "l"
|
|
tb.align["ID"] = "l"
|
|
|
|
return tb
|
|
|
|
|
|
def app_table(apps, deployments):
|
|
"""Returns a PrettyTable representation of the provided apps.
|
|
|
|
:param tasks: apps to render
|
|
:type tasks: [dict]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
deployment_map = {}
|
|
for deployment in deployments:
|
|
deployment_map[deployment['id']] = deployment
|
|
|
|
def get_cmd(app):
|
|
if app["cmd"] is not None:
|
|
return app["cmd"]
|
|
else:
|
|
return app["args"]
|
|
|
|
def get_container(app):
|
|
if app["container"] is not None:
|
|
return app["container"]["type"]
|
|
else:
|
|
return "mesos"
|
|
|
|
def get_health(app):
|
|
if app["healthChecks"]:
|
|
return "{}/{}".format(app["tasksHealthy"],
|
|
app["tasksRunning"])
|
|
else:
|
|
return EMPTY_ENTRY
|
|
|
|
def get_deployment(app):
|
|
deployment_ids = {deployment['id']
|
|
for deployment in app['deployments']}
|
|
|
|
actions = []
|
|
for deployment_id in deployment_ids:
|
|
deployment = deployment_map.get(deployment_id)
|
|
if deployment:
|
|
for action in deployment['currentActions']:
|
|
if action['app'] == app['id']:
|
|
actions.append(DEPLOYMENT_DISPLAY[action['action']])
|
|
|
|
if len(actions) == 0:
|
|
return EMPTY_ENTRY
|
|
elif len(actions) == 1:
|
|
return actions[0]
|
|
else:
|
|
return "({})".format(", ".join(actions))
|
|
|
|
fields = OrderedDict([
|
|
("ID", lambda a: a["id"]),
|
|
("MEM", lambda a: a["mem"]),
|
|
("CPUS", lambda a: a["cpus"]),
|
|
("TASKS", lambda a: "{}/{}".format(a["tasksRunning"],
|
|
a["instances"])),
|
|
("HEALTH", get_health),
|
|
("DEPLOYMENT", get_deployment),
|
|
("CONTAINER", get_container),
|
|
("CMD", get_cmd)
|
|
])
|
|
|
|
tb = table(fields, apps, sortby="ID")
|
|
tb.align["CMD"] = "l"
|
|
tb.align["ID"] = "l"
|
|
|
|
return tb
|
|
|
|
|
|
def app_task_table(tasks):
|
|
"""Returns a PrettyTable representation of the provided marathon tasks.
|
|
|
|
:param tasks: tasks to render
|
|
:type tasks: [dict]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
("APP", lambda t: t["appId"]),
|
|
("HEALTHY", lambda t:
|
|
all(check['alive'] for check in t.get('healthCheckResults', []))),
|
|
("STARTED", lambda t: t["startedAt"]),
|
|
("HOST", lambda t: t["host"]),
|
|
("ID", lambda t: t["id"])
|
|
])
|
|
|
|
tb = table(fields, tasks, sortby="APP")
|
|
tb.align["APP"] = "l"
|
|
tb.align["ID"] = "l"
|
|
|
|
return tb
|
|
|
|
|
|
def deployment_table(deployments):
|
|
"""Returns a PrettyTable representation of the provided marathon
|
|
deployments.
|
|
|
|
:param deployments: deployments to render
|
|
:type deployments: [dict]
|
|
:rtype: PrettyTable
|
|
|
|
"""
|
|
|
|
def get_action(deployment):
|
|
|
|
multiple_apps = len({action['app']
|
|
for action in deployment['currentActions']}) > 1
|
|
|
|
ret = []
|
|
for action in deployment['currentActions']:
|
|
try:
|
|
action_display = DEPLOYMENT_DISPLAY[action['action']]
|
|
except KeyError:
|
|
logger.exception('Missing action entry')
|
|
|
|
raise ValueError(
|
|
'Unknown Marathon action: {}'.format(action['action']))
|
|
|
|
if multiple_apps:
|
|
ret.append('{0} {1}'.format(action_display, action['app']))
|
|
else:
|
|
ret.append(action_display)
|
|
|
|
return '\n'.join(ret)
|
|
|
|
fields = OrderedDict([
|
|
('APP', lambda d: '\n'.join(d['affectedApps'])),
|
|
('ACTION', get_action),
|
|
('PROGRESS', lambda d: '{0}/{1}'.format(d['currentStep']-1,
|
|
d['totalSteps'])),
|
|
('ID', lambda d: d['id'])
|
|
])
|
|
|
|
tb = table(fields, deployments, sortby="APP")
|
|
tb.align['APP'] = 'l'
|
|
tb.align['ACTION'] = 'l'
|
|
tb.align['ID'] = 'l'
|
|
|
|
return tb
|
|
|
|
|
|
def service_table(services):
|
|
"""Returns a PrettyTable representation of the provided DCOS services.
|
|
|
|
:param services: services to render
|
|
:type services: [Framework]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
("NAME", lambda s: s['name']),
|
|
("HOST", lambda s: s['hostname']),
|
|
("ACTIVE", lambda s: s['active']),
|
|
("TASKS", lambda s: len(s['tasks'])),
|
|
("CPU", lambda s: s['resources']['cpus']),
|
|
("MEM", lambda s: s['resources']['mem']),
|
|
("DISK", lambda s: s['resources']['disk']),
|
|
("ID", lambda s: s['id']),
|
|
])
|
|
|
|
tb = table(fields, services, sortby="NAME")
|
|
tb.align["ID"] = 'l'
|
|
tb.align["NAME"] = 'l'
|
|
|
|
return tb
|
|
|
|
|
|
def _count_apps(group, group_dict):
|
|
"""Counts how many apps are registered for each group. Recursively
|
|
populates the profided `group_dict`, which maps group_id ->
|
|
(group, count).
|
|
|
|
:param group: nested group dictionary
|
|
:type group: dict
|
|
:param group_dict: group map that maps group_id -> (group, count)
|
|
:type group_dict: dict
|
|
:rtype: dict
|
|
|
|
"""
|
|
|
|
for child_group in group['groups']:
|
|
_count_apps(child_group, group_dict)
|
|
|
|
count = (len(group['apps']) +
|
|
sum(group_dict[child_group['id']][1]
|
|
for child_group in group['groups']))
|
|
|
|
group_dict[group['id']] = (group, count)
|
|
|
|
|
|
def group_table(groups):
|
|
"""Returns a PrettyTable representation of the provided marathon
|
|
groups
|
|
|
|
:param groups: groups to render
|
|
:type groups: [dict]
|
|
:rtype: PrettyTable
|
|
|
|
"""
|
|
|
|
group_dict = {}
|
|
for group in groups:
|
|
_count_apps(group, group_dict)
|
|
|
|
fields = OrderedDict([
|
|
('ID', lambda g: g[0]['id']),
|
|
('APPS', lambda g: g[1]),
|
|
])
|
|
|
|
tb = table(fields, group_dict.values(), sortby="ID")
|
|
tb.align['ID'] = 'l'
|
|
|
|
return tb
|
|
|
|
|
|
def package_table(packages):
|
|
"""Returns a PrettyTable representation of the provided DCOS packages
|
|
|
|
:param packages: packages to render
|
|
:type packages: [dict]
|
|
:rtype: PrettyTable
|
|
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
('NAME', lambda p: p['name']),
|
|
('VERSION', lambda p: p['version']),
|
|
('APP',
|
|
lambda p: '\n'.join(p['apps']) if p.get('apps') else EMPTY_ENTRY),
|
|
('COMMAND',
|
|
lambda p: p['command']['name'] if 'command' in p else EMPTY_ENTRY),
|
|
('DESCRIPTION', lambda p: p['description'])
|
|
])
|
|
|
|
tb = table(fields, packages, sortby="NAME")
|
|
tb.align['NAME'] = 'l'
|
|
tb.align['VERSION'] = 'l'
|
|
tb.align['APP'] = 'l'
|
|
tb.align['COMMAND'] = 'l'
|
|
tb.align['DESCRIPTION'] = 'l'
|
|
|
|
return tb
|
|
|
|
|
|
def package_search_table(search_results):
|
|
"""Returns a PrettyTable representation of the provided DCOS package
|
|
search results
|
|
|
|
:param search_results: search_results, in the format of
|
|
dcos.package.IndexEntries::as_dict()
|
|
:type search_results: [dict]
|
|
:rtype: PrettyTable
|
|
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
('NAME', lambda p: p['name']),
|
|
('VERSION', lambda p: p['currentVersion']),
|
|
('FRAMEWORK', lambda p: p['framework']),
|
|
('DESCRIPTION', lambda p: p['description'])
|
|
])
|
|
|
|
packages = []
|
|
for package in search_results['packages']:
|
|
package_ = copy.deepcopy(package)
|
|
packages.append(package_)
|
|
|
|
tb = table(fields, packages, sortby="NAME")
|
|
tb.align['NAME'] = 'l'
|
|
tb.align['VERSION'] = 'l'
|
|
tb.align['FRAMEWORK'] = 'l'
|
|
tb.align['DESCRIPTION'] = 'l'
|
|
|
|
return tb
|
|
|
|
|
|
def slave_table(slaves):
|
|
"""Returns a PrettyTable representation of the provided DCOS slaves
|
|
|
|
:param slaves: slaves to render. dicts from /mesos/state-summary
|
|
:type slaves: [dict]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
('HOSTNAME', lambda s: s['hostname']),
|
|
('IP', lambda s: mesos.parse_pid(s['pid'])[1]),
|
|
('ID', lambda s: s['id'])
|
|
])
|
|
|
|
tb = table(fields, slaves, sortby="HOSTNAME")
|
|
return tb
|
|
|
|
|
|
def _format_unix_timestamp(ts):
|
|
""" Formats a unix timestamp in a `dcos task ls --long` format.
|
|
|
|
:param ts: unix timestamp
|
|
:type ts: int
|
|
:rtype: str
|
|
"""
|
|
return datetime.datetime.fromtimestamp(ts).strftime('%b %d %H:%M')
|
|
|
|
|
|
def ls_long_table(files):
|
|
"""Returns a PrettyTable representation of `files`
|
|
|
|
:param files: Files to render. Of the form returned from the
|
|
mesos /files/browse.json endpoint.
|
|
:param files: [dict]
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
fields = OrderedDict([
|
|
('MODE', lambda f: f['mode']),
|
|
('NLINK', lambda f: f['nlink']),
|
|
('UID', lambda f: f['uid']),
|
|
('GID', lambda f: f['gid']),
|
|
('SIZE', lambda f: f['size']),
|
|
('DATE', lambda f: _format_unix_timestamp(int(f['mtime']))),
|
|
('PATH', lambda f: posixpath.basename(f['path']))])
|
|
|
|
tb = table(fields, files, sortby="PATH", header=False)
|
|
tb.align = 'r'
|
|
return tb
|
|
|
|
|
|
def table(fields, objs, **kwargs):
|
|
"""Returns a PrettyTable. `fields` represents the header schema of
|
|
the table. `objs` represents the objects to be rendered into
|
|
rows.
|
|
|
|
:param fields: An OrderedDict, where each element represents a
|
|
column. The key is the column header, and the
|
|
value is the function that transforms an element of
|
|
`objs` into a value for that column.
|
|
:type fields: OrderdDict(str, function)
|
|
:param objs: objects to render into rows
|
|
:type objs: [object]
|
|
:param **kwargs: kwargs to pass to `prettytable.PrettyTable`
|
|
:type **kwargs: dict
|
|
:rtype: PrettyTable
|
|
"""
|
|
|
|
tb = prettytable.PrettyTable(
|
|
[k.upper() for k in fields.keys()],
|
|
border=False,
|
|
hrules=prettytable.NONE,
|
|
vrules=prettytable.NONE,
|
|
left_padding_width=0,
|
|
right_padding_width=1,
|
|
**kwargs
|
|
)
|
|
|
|
# Set these explicitly due to a bug in prettytable where
|
|
# '0' values are not honored.
|
|
tb._left_padding_width = 0
|
|
tb._right_padding_width = 2
|
|
|
|
for obj in objs:
|
|
row = [fn(obj) for fn in fields.values()]
|
|
tb.add_row(row)
|
|
|
|
return tb
|