More Marathon API and CLI functions.

This commit is contained in:
Connor Doyle
2015-01-25 23:08:29 -08:00
parent 0047cf28b4
commit 71c11376c9
10 changed files with 294 additions and 27 deletions

View File

@@ -1,6 +1,8 @@
import json
import requests
from . import errors
from dcos.api import errors
class Client(object):
@@ -24,7 +26,7 @@ class Client(object):
:param path: Url path
:type path: str
:return: Constructed url
:returns: Constructed url
:rtype: str
"""
@@ -33,8 +35,51 @@ class Client(object):
port=self._port,
path=path)
def _sanitize_app_id(self, app_id):
# Add a leading '/' if necessary.
if not app_id.startswith('/'):
app_id = '/' + app_id
return app_id
def _response_to_error(self, response):
return Error('Error: {}'.format(response.json()['message']))
def get_app(self, app_id):
"""Get a list of known applications.
:param app_id: The ID of the application to suspend.
:type app_id: str
:returns: List of known applications.
:rtype: (list of dictionaries, Error)
"""
app_id = self._sanitize_app_id(app_id)
url = self._create_url('v2/apps' + app_id)
response = requests.get(url)
if response.status_code == 200:
app = response.json()['app']
return (app, None)
else:
return (None, self._response_to_error(response))
def get_apps(self):
"""Get a list of known applications.
:returns: List of known applications.
:rtype: (list of dictionaries, Error)
"""
url = self._create_url('v2/apps')
response = requests.get(url)
if response.status_code == 200:
apps = response.json()['apps']
return (apps, None)
else:
return (None, self._response_to_error(response))
def start_app(self, app_resource):
"""Create and start a new application
"""Create and start a new application.
:param app_resource: Application resource
:type app_resource: dict, bytes, or file
@@ -48,11 +93,68 @@ class Client(object):
if response.status_code == 201:
return (True, None)
else:
return (
None,
Error(
'Error talking to Marathon: {}'.format(
response.json()['message'])))
return (None, self._response_to_error(response))
def scale_app(self, app_id, instances, force=None):
"""Scales an application to zero instances.
:param app_id: The ID of the application to suspend.
:type app_id: str
:param instances: The requested number of instances.
:type instances: int
:param force: Whether to override running deployments.
:type force: bool
:returns: The resulting deployment ID.
:rtype: (bool, Error)
"""
if force is None:
force = False
app_id = self._sanitize_app_id(app_id)
forceQuery = ''
if force:
forceQuery = '?force=true'
url = self._create_url('v2/apps' + app_id + forceQuery)
scale_json = json.loads('{ "instances": %d }' % int(instances))
response = requests.put(url, json=scale_json)
if response.status_code == 200:
deployment = response.json()['deploymentId']
return (deployment, None)
else:
return (None, self._response_to_error(response))
def suspend_app(self, app_id, force=None):
"""Scales an application to zero instances.
:param app_id: The ID of the application to suspend.
:type app_id: str
:param force: Whether to override running deployments.
:type force: bool
:returns: The resulting deployment ID.
:rtype: (bool, Error)
"""
return self.scale_app(app_id, 0, force)
def remove_app(self, app_id):
"""Completely removes the requested application.
:param app_id: The ID of the application to suspend.
:type app_id: str
:returns: Status of trying to remove the application.
:rtype: (bool, Error)
"""
app_id = self._sanitize_app_id(app_id)
url = self._create_url('v2/apps' + app_id + '?force=true')
response = requests.delete(url)
if response.status_code == 200:
return (True, None)
else:
return (None, self._response_to_error(response))
class Error(errors.Error):

View File

@@ -1,8 +1,19 @@
import json
import pystache
from jsonschema import validate
import pystache
class Client(object):
"""Class for talking to the package server."""
class Package(object):
"""Representation of a software package."""
class PackageRegistry(object):
"""Representation of a package registry."""
def render_template(template, params):

View File

@@ -15,7 +15,7 @@ import os
import docopt
import toml
from ...api import config, constants
from dcos.api import config, constants
def main():
@@ -34,7 +34,7 @@ def main():
elif args['config'] and args['<value>'] is None:
toml_config = config.Toml.load_from_path(config_path)
print(config[args['<name>']])
print(toml_config[args['<name>']])
elif args['config']:
toml_config = config.Toml.load_from_path(config_path)

View File

@@ -19,7 +19,7 @@ import subprocess
import docopt
from ..api import constants
from dcos.api import constants
def main():

View File

@@ -1,7 +1,12 @@
"""
Usage:
dcos marathon info
dcos marathon list
dcos marathon describe <app_id>
dcos marathon start <app_resource>
dcos marathon scale <app_id> <instances> [--force]
dcos marathon suspend <app_id> [--force]
dcos marathon remove <app_id>
dcos marathon --help
dcos marathon --version
@@ -10,11 +15,12 @@ Options:
--version Show version
"""
import json
import os
import docopt
from ...api import config, constants, marathon, options
from dcos.api import config, constants, marathon, options
def main():
@@ -25,22 +31,99 @@ def main():
if args['marathon'] and args['info']:
return _info()
elif args['marathon'] and args['list']:
toml_config = config.Toml.load_from_path(config_path)
return _list(toml_config)
elif args['marathon'] and args['describe']:
toml_config = config.Toml.load_from_path(config_path)
return _describe(args['<app_id>'], toml_config)
elif args['marathon'] and args['start']:
toml_config = config.Toml.load_from_path(config_path)
return _start(args['<app_resource>'], toml_config)
elif args['marathon'] and args['scale']:
toml_config = config.Toml.load_from_path(config_path)
return _scale(args['<app_id>'],
args['<instances>'],
args['--force'],
toml_config)
elif args['marathon'] and args['suspend']:
toml_config = config.Toml.load_from_path(config_path)
return _suspend(args['<app_id>'], args['--force'], toml_config)
elif args['marathon'] and args['remove']:
toml_config = config.Toml.load_from_path(config_path)
return _remove(args['<app_id>'], toml_config)
else:
print(options.make_generic_usage_error(__doc__))
return 1
def _info():
"""Print marathon cli information
"""Print marathon cli information.
:returns: Process status
:rtype: int
"""
print('Deploy and manage containers for Mesos')
print('Deploy and manage applications on Apache Mesos')
return 0
def _create_client(config):
"""Creates a Marathon client with the supplied configuration.
:param config: Configuration dictionary
:type config: config.Toml
:returns: Marathon client
:rtype: dcos.api.marathon.Client
"""
return marathon.Client(config['marathon.host'], config['marathon.port'])
def _list(config):
"""Lists known Marathon applications.
:param config: Configuration dictionary
:type config: config.Toml
:returns: Process status
:rtype: int
"""
client = _create_client(config)
apps, err = client.get_apps()
if err is not None:
print(err.error())
return 1
if not apps:
print("No apps to list.")
for app in apps:
print(app['id'])
return 0
def _describe(app_id, config):
"""Show details of a Marathon applications.
:param app_id: ID of the app to suspend
:type app_id: str
:param config: Configuration dictionary
:type config: config.Toml
:returns: Process status
:rtype: int
"""
client = _create_client(config)
app, err = client.get_app(app_id)
if err is not None:
print(err.error())
return 1
print(json.dumps(app,
sort_keys=True,
indent=2))
return 0
@@ -54,7 +137,7 @@ def _start(app_resource_path, config):
:returns: Process status
:rtype: int
"""
client = marathon.Client(config['marathon.host'], config['marathon.port'])
client = _create_client(config)
with open(app_resource_path) as app_resource_file:
success, err = client.start_app(app_resource_file)
@@ -63,3 +146,69 @@ def _start(app_resource_path, config):
return 1
return 0
def _scale(app_id, instances, force, config):
"""Suspends a running Marathon application.
:param app_id: ID of the app to suspend
:type app_id: str
:param instances: The requested number of instances.
:type instances: int
:param config: Configuration dictionary
:type config: config.Toml
:returns: Process status
:rtype: int
"""
client = _create_client(config)
deployment, err = client.scale_app(app_id, instances, force)
if err is not None:
print(err.error())
return 1
print('Created deployment {}'.format(deployment))
return 0
def _suspend(app_id, force, config):
"""Suspends a running Marathon application.
:param app_id: ID of the app to suspend
:type app_id: str
:param config: Configuration dictionary
:type config: config.Toml
:returns: Process status
:rtype: int
"""
client = _create_client(config)
deployment, err = client.suspend_app(app_id, force)
if err is not None:
print(err.error())
return 1
print('Created deployment {}'.format(deployment))
return 0
def _remove(app_id, config):
"""Remove a Marathon application.
:param app_id: ID of the app to remove
:type app_id: str
:param config: Configuration dictionary
:type config: config.Toml
:returns: Process status
:rtype: int
"""
client = _create_client(config)
success, err = client.remove_app(app_id)
if err is not None:
print(err.error())
return 1
return 0

View File

@@ -9,7 +9,7 @@ Options:
import docopt
from ..api import constants
from dcos.api import constants
def main():
@@ -18,7 +18,7 @@ def main():
version='dcos-package version {}'.format(constants.version))
if args['package'] and args['info']:
print('Work with DCOS packages.')
print('Manage DCOS packages and upstream registries')
else:
print(args)

View File

@@ -12,7 +12,7 @@ import subprocess
import docopt
from ...api import constants
from dcos.api import constants
def main():
@@ -21,7 +21,7 @@ def main():
version='dcos-subcommand version {}'.format(constants.version))
if args['subcommand'] and args['info']:
print('Manage DCOS external commands')
print('Manage external DCOS commands')
elif args['subcommand'] and args['install'] and args['python']:
print('Trying to install a python subcommand')
command = ['pip', 'install', args['<uri>']]

View File

@@ -74,6 +74,7 @@ setup(
install_requires=[
'docopt',
'jsonschema',
'pystache',
'requests',
'toml',
],

View File

@@ -0,0 +1,11 @@
{
"id": "test-app",
"cmd": "sleep 1000",
"cpus": 0.1,
"mem": 16,
"instances": 1
"labels": [
"PACKAGE_ID": "test-app",
"PACKAGE_VERSION": "1.2.3"
]
}

View File

@@ -1,7 +0,0 @@
{
"cmd": "sleep 1000",
"cpus": 0.25,
"id": "sleeping-app",
"instances": 1,
"mem": 50
}