dcos node

This commit is contained in:
Michael Gummelt
2015-06-29 18:36:31 -07:00
parent aa88ed2c2e
commit aff8afae2e
13 changed files with 244 additions and 17 deletions

View File

92
cli/dcoscli/node/main.py Normal file
View File

@@ -0,0 +1,92 @@
"""Manage DCOS nodes
Usage:
dcos node --info
dcos node [--json]
Options:
-h, --help Show this screen
--info Show a short description of this subcommand
--json Print json-formatted nodes
--version Show version
"""
import dcoscli
import docopt
from dcos import cmds, emitting, errors, mesos, util
from dcos.errors import DCOSException
from dcoscli import tables
logger = util.get_logger(__name__)
emitter = emitting.FlatEmitter()
def main():
try:
return _main()
except DCOSException as e:
emitter.publish(e)
return 1
def _main():
util.configure_logger_from_environ()
args = docopt.docopt(
__doc__,
version="dcos-node version {}".format(dcoscli.version))
return cmds.execute(_cmds(), args)
def _cmds():
"""
:returns: All of the supported commands
:rtype: [Command]
"""
return [
cmds.Command(
hierarchy=['node', '--info'],
arg_keys=[],
function=_info),
cmds.Command(
hierarchy=['node'],
arg_keys=['--json'],
function=_list),
]
def _info():
"""Print node cli information.
:returns: process return code
:rtype: int
"""
emitter.publish(__doc__.split('\n')[0])
return 0
def _list(json_):
"""List dcos nodes
:param json_: If true, output json.
Otherwise, output a human readable table.
:type json_: bool
:returns: process return code
:rtype: int
"""
client = mesos.MesosClient()
slaves = client.get_state_summary()['slaves']
if json_:
emitter.publish(slaves)
else:
table = tables.slave_table(slaves)
output = str(table)
if output:
emitter.publish(output)
else:
emitter.publish(errors.DefaultError('No slaves found.'))

View File

@@ -91,7 +91,7 @@ def _service(inactive, is_json):
"""List dcos services """List dcos services
:param inactive: If True, include completed tasks :param inactive: If True, include completed tasks
:type completed: bool :type inactive: bool
:param is_json: If true, output json. :param is_json: If true, output json.
Otherwise, output a human readable table. Otherwise, output a human readable table.
:type is_json: bool :type is_json: bool

View File

@@ -1,7 +1,7 @@
import copy import copy
from collections import OrderedDict from collections import OrderedDict
from dcos import util from dcos import mesos, util
def task_table(tasks): def task_table(tasks):
@@ -276,3 +276,21 @@ def package_search_table(search_results):
tb.align['DESCRIPTION'] = 'l' tb.align['DESCRIPTION'] = 'l'
return tb 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 = util.table(fields, slaves, sortby="HOSTNAME")
return tb

View File

@@ -97,6 +97,7 @@ setup(
'dcos-package=dcoscli.package.main:main', 'dcos-package=dcoscli.package.main:main',
'dcos-service=dcoscli.service.main:main', 'dcos-service=dcoscli.service.main:main',
'dcos-task=dcoscli.task.main:main', 'dcos-task=dcoscli.task.main:main',
'dcos-node=dcoscli.node.main:main'
], ],
}, },

40
cli/tests/fixtures/node.py vendored Normal file
View File

@@ -0,0 +1,40 @@
def slave_fixture():
""" Slave node fixture.
:rtype: dict
"""
return {
"TASK_ERROR": 0,
"TASK_FAILED": 0,
"TASK_FINISHED": 0,
"TASK_KILLED": 0,
"TASK_LOST": 0,
"TASK_RUNNING": 0,
"TASK_STAGING": 0,
"TASK_STARTING": 0,
"active": True,
"attributes": {},
"framework_ids": [],
"hostname": "dcos-01",
"id": "20150630-004309-1695027628-5050-1649-S0",
"offered_resources": {
"cpus": 0,
"disk": 0,
"mem": 0
},
"pid": "slave(1)@172.17.8.101:5051",
"registered_time": 1435625024.42234,
"resources": {
"cpus": 4,
"disk": 10823,
"mem": 2933,
"ports": ("[1025-2180, 2182-3887, 3889-5049, 5052-8079, " +
"8082-8180, 8182-65535]")
},
"used_resources": {
"cpus": 0,
"disk": 0,
"mem": 0
}
}

View File

@@ -20,6 +20,7 @@ Available DCOS commands:
\tconfig \tGet and set DCOS CLI configuration properties \tconfig \tGet and set DCOS CLI configuration properties
\thelp \tDisplay command line usage information \thelp \tDisplay command line usage information
\tmarathon \tDeploy and manage applications on the DCOS \tmarathon \tDeploy and manage applications on the DCOS
\tnode \tManage DCOS nodes
\tpackage \tInstall and manage DCOS software packages \tpackage \tInstall and manage DCOS software packages
\tservice \tManage DCOS services \tservice \tManage DCOS services
\ttask \tManage DCOS tasks \ttask \tManage DCOS tasks

View File

@@ -39,6 +39,7 @@ Available DCOS commands:
\tconfig \tGet and set DCOS CLI configuration properties \tconfig \tGet and set DCOS CLI configuration properties
\thelp \tDisplay command line usage information \thelp \tDisplay command line usage information
\tmarathon \tDeploy and manage applications on the DCOS \tmarathon \tDeploy and manage applications on the DCOS
\tnode \tManage DCOS nodes
\tpackage \tInstall and manage DCOS software packages \tpackage \tInstall and manage DCOS software packages
\tservice \tManage DCOS services \tservice \tManage DCOS services
\ttask \tManage DCOS tasks \ttask \tManage DCOS tasks

View File

@@ -0,0 +1,44 @@
import json
import dcos.util as util
from dcos.util import create_schema
from ..fixtures.node import slave_fixture
from .common import assert_command, assert_lines, exec_command
def test_help():
stdout = b"""Manage DCOS nodes
Usage:
dcos node --info
dcos node [--json]
Options:
-h, --help Show this screen
--info Show a short description of this subcommand
--json Print json-formatted nodes
--version Show version
"""
assert_command(['dcos', 'node', '--help'], stdout=stdout)
def test_info():
stdout = b"Manage DCOS nodes\n"
assert_command(['dcos', 'node', '--info'], stdout=stdout)
def test_node():
returncode, stdout, stderr = exec_command(['dcos', 'node', '--json'])
assert returncode == 0
assert stderr == b''
nodes = json.loads(stdout.decode('utf-8'))
schema = create_schema(slave_fixture())
for node in nodes:
assert not util.validate_json(node, schema)
def test_node_table():
assert_lines(['dcos', 'node'], 2)

View File

@@ -7,8 +7,7 @@ import pytest
from ..fixtures.service import framework_fixture from ..fixtures.service import framework_fixture
from .common import (assert_command, assert_lines, delete_zk_nodes, from .common import (assert_command, assert_lines, delete_zk_nodes,
exec_command, get_services, service_shutdown, get_services, service_shutdown, watch_all_deployments)
watch_all_deployments)
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@@ -50,8 +49,6 @@ def test_info():
def test_service(): def test_service():
returncode, stdout, stderr = exec_command(['dcos', 'service', '--json'])
services = get_services(1) services = get_services(1)
schema = _get_schema(framework_fixture()) schema = _get_schema(framework_fixture())
@@ -63,17 +60,6 @@ def test_service_table():
assert_lines(['dcos', 'service'], 2) assert_lines(['dcos', 'service'], 2)
def _get_schema(service):
schema = create_schema(service.dict())
schema['required'].remove('reregistered_time')
schema['required'].remove('pid')
schema['properties']['offered_resources']['required'].remove('ports')
schema['properties']['resources']['required'].remove('ports')
schema['properties']['used_resources']['required'].remove('ports')
return schema
def test_service_inactive(zk_znode): def test_service_inactive(zk_znode):
# install cassandra # install cassandra
stdout = b"""The Apache Cassandra DCOS Service implementation is alpha \ stdout = b"""The Apache Cassandra DCOS Service implementation is alpha \
@@ -122,3 +108,14 @@ Thank you for installing the Apache Cassandra DCOS Service.
# assert marathon is only listed with --inactive # assert marathon is only listed with --inactive
get_services(1, ['--inactive']) get_services(1, ['--inactive'])
def _get_schema(service):
schema = create_schema(service.dict())
schema['required'].remove('reregistered_time')
schema['required'].remove('pid')
schema['properties']['offered_resources']['required'].remove('ports')
schema['properties']['resources']['required'].remove('ports')
schema['properties']['used_resources']['required'].remove('ports')
return schema

View File

@@ -0,0 +1,2 @@
HOSTNAME IP ID
dcos-01 172.17.8.101 20150630-004309-1695027628-5050-1649-S0

View File

@@ -2,6 +2,7 @@ from dcoscli import tables
from ..fixtures.marathon import (app_fixture, app_task_fixture, from ..fixtures.marathon import (app_fixture, app_task_fixture,
deployment_fixture, group_fixture) deployment_fixture, group_fixture)
from ..fixtures.node import slave_fixture
from ..fixtures.package import package_fixture, search_result_fixture from ..fixtures.package import package_fixture, search_result_fixture
from ..fixtures.service import framework_fixture from ..fixtures.service import framework_fixture
from ..fixtures.task import task_fixture from ..fixtures.task import task_fixture
@@ -55,6 +56,12 @@ def test_package_search_table():
'tests/unit/data/package_search.txt') 'tests/unit/data/package_search.txt')
def test_node_table():
_test_table(tables.slave_table,
slave_fixture,
'tests/unit/data/node.txt')
def _test_table(table_fn, fixture_fn, path): def _test_table(table_fn, fixture_fn, path):
table = table_fn([fixture_fn()]) table = table_fn([fixture_fn()])
with open(path) as f: with open(path) as f:

View File

@@ -90,6 +90,16 @@ class MesosClient:
url = self.slave_url(slave_id, 'state.json') url = self.slave_url(slave_id, 'state.json')
return http.get(url).json() return http.get(url).json()
def get_state_summary(self):
"""Get the Mesos master state summary json object
:returns: Mesos' master state summary json object
:rtype: dict
"""
url = self.master_url('master/state-summary')
return http.get(url).json()
def shutdown_framework(self, framework_id): def shutdown_framework(self, framework_id):
"""Shuts down a Mesos framework """Shuts down a Mesos framework
@@ -696,6 +706,20 @@ class MesosFile(object):
return "{0}:{1}".format(self._task['id'], self._path) return "{0}:{1}".format(self._task['id'], self._path)
def parse_pid(pid):
""" Parse the mesos pid string,
:param pid: pid of the form "id@ip:port"
:type pid: str
:returns: parsed pid
:rtype: (str, str, str)
"""
id_, second = pid.split('@')
ip, port = second.split(':')
return id_, ip, port
def _merge(d, keys): def _merge(d, keys):
""" Merge multiple lists from a dictionary into one iterator. """ Merge multiple lists from a dictionary into one iterator.
e.g. _merge({'a': [1, 2], 'b': [3]}, ['a', 'b']) -> e.g. _merge({'a': [1, 2], 'b': [3]}, ['a', 'b']) ->