python-marathon-0.8.10/0000755000175000017500000000000013034033706013703 5ustar chuckchuckpython-marathon-0.8.10/PKG-INFO0000644000175000017500000000156013034033706015002 0ustar chuckchuckMetadata-Version: 1.1 Name: marathon Version: 0.8.10 Summary: Marathon Client Library Home-page: https://github.com/thefactory/marathon-python Author: Mike Babineau Author-email: michael.babineau@gmail.com License: MIT Description: Python interface to the Mesos Marathon REST API. Platform: Posix; MacOS X; Windows Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules python-marathon-0.8.10/marathon.egg-info/0000755000175000017500000000000013034033706017206 5ustar chuckchuckpython-marathon-0.8.10/marathon.egg-info/top_level.txt0000644000175000017500000000001113034033706021730 0ustar chuckchuckmarathon python-marathon-0.8.10/marathon.egg-info/requires.txt0000644000175000017500000000002013034033706021576 0ustar chuckchuckrequests>=2.0.0 python-marathon-0.8.10/marathon.egg-info/PKG-INFO0000644000175000017500000000156013034033706020305 0ustar chuckchuckMetadata-Version: 1.1 Name: marathon Version: 0.8.10 Summary: Marathon Client Library Home-page: https://github.com/thefactory/marathon-python Author: Mike Babineau Author-email: michael.babineau@gmail.com License: MIT Description: Python interface to the Mesos Marathon REST API. Platform: Posix; MacOS X; Windows Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules python-marathon-0.8.10/marathon.egg-info/pbr.json0000644000175000017500000000005712567131367020702 0ustar chuckchuck{"is_release": false, "git_version": "779e974"}python-marathon-0.8.10/marathon.egg-info/dependency_links.txt0000644000175000017500000000000113034033706023254 0ustar chuckchuck python-marathon-0.8.10/marathon.egg-info/SOURCES.txt0000644000175000017500000000120013034033706021063 0ustar chuckchuckLICENSE MANIFEST.in setup.cfg setup.py marathon/__init__.py marathon/_compat.py marathon/client.py marathon/exceptions.py marathon/util.py marathon.egg-info/PKG-INFO marathon.egg-info/SOURCES.txt marathon.egg-info/dependency_links.txt marathon.egg-info/pbr.json marathon.egg-info/requires.txt marathon.egg-info/top_level.txt marathon/models/__init__.py marathon/models/app.py marathon/models/base.py marathon/models/constraint.py marathon/models/container.py marathon/models/deployment.py marathon/models/endpoint.py marathon/models/events.py marathon/models/group.py marathon/models/info.py marathon/models/queue.py marathon/models/task.pypython-marathon-0.8.10/setup.py0000755000175000017500000000222713034033575015427 0ustar chuckchuck#!/usr/bin/env python import sys from setuptools import setup extra = {} if sys.version_info >= (3,): extra['use_2to3'] = True setup( name='marathon', version='0.8.10', description='Marathon Client Library', long_description="""Python interface to the Mesos Marathon REST API.""", author='Mike Babineau', author_email='michael.babineau@gmail.com', install_requires=['requests>=2.0.0'], url='https://github.com/thefactory/marathon-python', packages=['marathon', 'marathon.models'], license='MIT', platforms='Posix; MacOS X; Windows', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules' ], **extra ) python-marathon-0.8.10/MANIFEST.in0000644000175000017500000000002013012736162015433 0ustar chuckchuckinclude LICENSE python-marathon-0.8.10/LICENSE0000644000175000017500000000212112551307633014712 0ustar chuckchuckThe MIT License (MIT) Copyright (c) 2014 The Factory Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.python-marathon-0.8.10/marathon/0000755000175000017500000000000013034033706015514 5ustar chuckchuckpython-marathon-0.8.10/marathon/__init__.py0000644000175000017500000000040612704027537017635 0ustar chuckchuckimport logging from .client import MarathonClient from .models import MarathonResource, MarathonApp, MarathonTask, MarathonConstraint from .exceptions import MarathonError, MarathonHttpError, NotFoundError, InvalidChoiceError log = logging.getLogger(__name__) python-marathon-0.8.10/marathon/util.py0000644000175000017500000000335012730027610017043 0ustar chuckchuckimport collections import datetime try: import json except ImportError: import simplejson as json import re from ._compat import string_types def is_stringy(obj): return isinstance(obj, string_types) class MarathonJsonEncoder(json.JSONEncoder): """Custom JSON encoder for Marathon object serialization.""" def default(self, obj): if hasattr(obj, 'json_repr'): return self.default(obj.json_repr()) if isinstance(obj, datetime.datetime): return obj.strftime('%Y-%m-%dT%H:%M:%S.%fZ') if isinstance(obj, collections.Iterable) and not is_stringy(obj): try: return {k: self.default(v) for k, v in obj.items()} except AttributeError: return [self.default(e) for e in obj] return obj class MarathonMinimalJsonEncoder(json.JSONEncoder): """Custom JSON encoder for Marathon object serialization.""" def default(self, obj): if hasattr(obj, 'json_repr'): return self.default(obj.json_repr(minimal=True)) if isinstance(obj, datetime.datetime): return obj.strftime('%Y-%m-%dT%H:%M:%S.%fZ') if isinstance(obj, collections.Iterable) and not is_stringy(obj): try: return {k: self.default(v) for k, v in obj.items() if (v or v in (False, 0))} except AttributeError: return [self.default(e) for e in obj if (e or e in (False, 0))] return obj def to_camel_case(snake_str): words = snake_str.split('_') return words[0] + ''.join(w.title() for w in words[1:]) def to_snake_case(camel_str): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() python-marathon-0.8.10/marathon/exceptions.py0000644000175000017500000000226413012736162020255 0ustar chuckchuckclass MarathonError(Exception): pass class MarathonHttpError(MarathonError): def __init__(self, response): """ :param :class:`requests.Response` response: HTTP response """ self.error_message = response.reason or '' if response.content: content = response.json() self.error_message = content.get('message', self.error_message) self.error_details = content.get('details') self.status_code = response.status_code super(MarathonHttpError, self).__init__(self.__str__()) def __repr__(self): return 'MarathonHttpError: HTTP %s returned with message, "%s"' % \ (self.status_code, self.error_message) def __str__(self): return self.__repr__() class NotFoundError(MarathonHttpError): pass class InternalServerError(MarathonHttpError): pass class InvalidChoiceError(MarathonError): def __init__(self, param, value, options): super(InvalidChoiceError, self).__init__( 'Invalid choice "{value}" for param "{param}". Must be one of {options}'.format( param=param, value=value, options=options ) ) python-marathon-0.8.10/marathon/_compat.py0000644000175000017500000000025512704027201017506 0ustar chuckchuck""" Support for python 2 & 3, ripped pieces from six.py """ import sys PY3 = sys.version_info[0] == 3 if PY3: string_types = str, else: string_types = basestring, python-marathon-0.8.10/marathon/client.py0000644000175000017500000007150613024334720017354 0ustar chuckchuckimport itertools import time try: import json except ImportError: import simplejson as json import requests import requests.exceptions import marathon from .models import MarathonApp, MarathonDeployment, MarathonGroup, MarathonInfo, MarathonTask, MarathonEndpoint, MarathonQueueItem from .exceptions import InternalServerError, NotFoundError, MarathonHttpError, MarathonError from .models.events import EventFactory from .util import MarathonJsonEncoder, MarathonMinimalJsonEncoder class MarathonClient(object): """Client interface for the Marathon REST API.""" def __init__(self, servers, username=None, password=None, timeout=10, session=None, auth_token=None, verify=True): """Create a MarathonClient instance. If multiple servers are specified, each will be tried in succession until a non-"Connection Error"-type response is received. Servers are expected to have the same username and password. :param servers: One or a priority-ordered list of Marathon URLs (e.g., 'http://host:8080' or ['http://host1:8080','http://host2:8080']) :type servers: str or list[str] :param str username: Basic auth username :param str password: Basic auth password :param int timeout: Timeout (in seconds) for requests to Marathon :param str auth_token: Token-based auth token, used with DCOS + Oauth :param bool verify: Enable SSL certificate verification """ if session is None: self.session = requests.Session() else: self.session = session self.servers = servers if isinstance(servers, list) else [servers] self.auth = (username, password) if username and password else None self.verify = verify self.timeout = timeout self.auth_token = auth_token if self.auth and self.auth_token: raise ValueError("Can't specify both auth token and username/password. Must select " "one type of authentication.") def __repr__(self): return 'Connection:%s' % self.servers @staticmethod def _parse_response(response, clazz, is_list=False, resource_name=None): """Parse a Marathon response into an object or list of objects.""" target = response.json()[ resource_name] if resource_name else response.json() if is_list: return [clazz.from_json(resource) for resource in target] else: return clazz.from_json(target) def _do_request(self, method, path, params=None, data=None): """Query Marathon server.""" headers = { 'Content-Type': 'application/json', 'Accept': 'application/json'} if self.auth_token: headers['Authorization'] = "token={}".format(self.auth_token) response = None servers = list(self.servers) while servers and response is None: server = servers.pop(0) url = ''.join([server.rstrip('/'), path]) try: response = self.session.request( method, url, params=params, data=data, headers=headers, auth=self.auth, timeout=self.timeout, verify=self.verify) marathon.log.info('Got response from %s', server) except requests.exceptions.RequestException as e: marathon.log.error( 'Error while calling %s: %s', url, str(e)) if response is None: raise MarathonError('No remaining Marathon servers to try') if response.status_code >= 500: marathon.log.error('Got HTTP {code}: {body}'.format( code=response.status_code, body=response.text)) raise InternalServerError(response) elif response.status_code >= 400: marathon.log.error('Got HTTP {code}: {body}'.format( code=response.status_code, body=response.text)) if response.status_code == 404: raise NotFoundError(response) else: raise MarathonHttpError(response) elif response.status_code >= 300: marathon.log.warn('Got HTTP {code}: {body}'.format( code=response.status_code, body=response.text)) else: marathon.log.debug('Got HTTP {code}: {body}'.format( code=response.status_code, body=response.text)) return response def _do_sse_request(self, path): """Query Marathon server for events.""" for server in list(self.servers): url = ''.join([server.rstrip('/'), path]) try: response = requests.get( url, stream=True, headers={'Accept': 'text/event-stream'}, auth=self.auth ) except Exception as e: marathon.log.error('Error while calling %s: %s', url, e.message) if response.ok: return response.iter_lines() raise MarathonError('No remaining Marathon servers to try') def list_endpoints(self): """List the current endpoints for all applications :returns: list of endpoints :rtype: list[`MarathonEndpoint`] """ return MarathonEndpoint.from_tasks(self.list_tasks()) def create_app(self, app_id, app): """Create and start an app. :param str app_id: application ID :param :class:`marathon.models.app.MarathonApp` app: the application to create :returns: the created app (on success) :rtype: :class:`marathon.models.app.MarathonApp` or False """ app.id = app_id data = app.to_json() response = self._do_request('POST', '/v2/apps', data=data) if response.status_code == 201: return self._parse_response(response, MarathonApp) else: return False def list_apps(self, cmd=None, embed_tasks=False, embed_counts=False, embed_deployments=False, embed_readiness=False, embed_last_task_failure=False, embed_failures=False, embed_task_stats=False, app_id=None, **kwargs): """List all apps. :param str cmd: if passed, only show apps with a matching `cmd` :param bool embed_tasks: embed tasks in result :param bool embed_counts: embed all task counts :param bool embed_deployments: embed all deployment identifier :param bool embed_readiness: embed all readiness check results :param bool embed_last_task_failure: embeds the last task failure :param bool embed_failures: shorthand for embed_last_task_failure :param bool embed_task_stats: embed task stats in result :param bool app_id: if passed, only show apps with with an 'id' that matches or contains this value :param kwargs: arbitrary search filters :returns: list of applications :rtype: list[:class:`marathon.models.app.MarathonApp`] """ params = {} if cmd: params['cmd'] = cmd if app_id: params['id'] = app_id embed_params = { 'app.tasks': embed_tasks, 'app.counts': embed_counts, 'app.deployments': embed_deployments, 'app.readiness': embed_readiness, 'app.lastTaskFailure': embed_last_task_failure, 'app.failures': embed_failures, 'app.taskStats': embed_task_stats } filtered_embed_params = [k for (k, v) in embed_params.items() if v] if filtered_embed_params: params['embed'] = filtered_embed_params response = self._do_request('GET', '/v2/apps', params=params) apps = self._parse_response( response, MarathonApp, is_list=True, resource_name='apps') for k, v in kwargs.items(): apps = [o for o in apps if getattr(o, k) == v] return apps def get_app(self, app_id, embed_tasks=False, embed_counts=False, embed_deployments=False, embed_readiness=False, embed_last_task_failure=False, embed_failures=False, embed_task_stats=False): """Get a single app. :param str app_id: application ID :param bool embed_tasks: embed tasks in result :param bool embed_counts: embed all task counts :param bool embed_deployments: embed all deployment identifier :param bool embed_readiness: embed all readiness check results :param bool embed_last_task_failure: embeds the last task failure :param bool embed_failures: shorthand for embed_last_task_failure :param bool embed_task_stats: embed task stats in result :returns: application :rtype: :class:`marathon.models.app.MarathonApp` """ params = {} embed_params = { 'app.tasks': embed_tasks, 'app.counts': embed_counts, 'app.deployments': embed_deployments, 'app.readiness': embed_readiness, 'app.lastTaskFailure': embed_last_task_failure, 'app.failures': embed_failures, 'app.taskStats': embed_task_stats } filtered_embed_params = [k for (k, v) in embed_params.items() if v] if filtered_embed_params: params['embed'] = filtered_embed_params response = self._do_request( 'GET', '/v2/apps/{app_id}'.format(app_id=app_id), params=params) return self._parse_response(response, MarathonApp, resource_name='app') def restart_app(self, app_id, force=False): """ Restarts given application by app_id :param str app_id: application ID :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} response = self._do_request( 'POST', '/v2/apps/{app_id}/restart'.format(app_id=app_id), params=params) return response.json() def update_app(self, app_id, app, force=False, minimal=True): """Update an app. Applies writable settings in `app` to `app_id` Note: this method can not be used to rename apps. :param str app_id: target application ID :param app: application settings :type app: :class:`marathon.models.app.MarathonApp` :param bool force: apply even if a deployment is in progress :param bool minimal: ignore nulls and empty collections :returns: a dict containing the deployment id and version :rtype: dict """ # Changes won't take if version is set - blank it for convenience app.version = None params = {'force': force} data = app.to_json(minimal=minimal) response = self._do_request( 'PUT', '/v2/apps/{app_id}'.format(app_id=app_id), params=params, data=data) return response.json() def update_apps(self, apps, force=False, minimal=True): """Update multiple apps. Applies writable settings in elements of apps either by upgrading existing ones or creating new ones :param apps: sequence of application settings :param bool force: apply even if a deployment is in progress :param bool minimal: ignore nulls and empty collections :returns: a dict containing the deployment id and version :rtype: dict """ json_repr_apps = [] for app in apps: # Changes won't take if version is set - blank it for convenience app.version = None json_repr_apps.append(app.json_repr(minimal=minimal)) params = {'force': force} encoder = MarathonMinimalJsonEncoder if minimal else MarathonJsonEncoder data = json.dumps(json_repr_apps, cls=encoder, sort_keys=True) response = self._do_request( 'PUT', '/v2/apps', params=params, data=data) return response.json() def rollback_app(self, app_id, version, force=False): """Roll an app back to a previous version. :param str app_id: application ID :param str version: application version :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} data = json.dumps({'version': version}) response = self._do_request( 'PUT', '/v2/apps/{app_id}'.format(app_id=app_id), params=params, data=data) return response.json() def delete_app(self, app_id, force=False): """Stop and destroy an app. :param str app_id: application ID :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} response = self._do_request( 'DELETE', '/v2/apps/{app_id}'.format(app_id=app_id), params=params) return response.json() def scale_app(self, app_id, instances=None, delta=None, force=False): """Scale an app. Scale an app to a target number of instances (with `instances`), or scale the number of instances up or down by some delta (`delta`). If the resulting number of instances would be negative, desired instances will be set to zero. If both `instances` and `delta` are passed, use `instances`. :param str app_id: application ID :param int instances: [optional] the number of instances to scale to :param int delta: [optional] the number of instances to scale up or down by :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ if instances is None and delta is None: marathon.log.error('instances or delta must be passed') return try: app = self.get_app(app_id) except NotFoundError: marathon.log.error('App "{app}" not found'.format(app=app_id)) return desired = instances if instances is not None else ( app.instances + delta) return self.update_app(app.id, MarathonApp(instances=desired), force=force) def create_group(self, group): """Create and start a group. :param :class:`marathon.models.group.MarathonGroup` group: the group to create :returns: success :rtype: dict containing the version ID """ data = group.to_json() response = self._do_request('POST', '/v2/groups', data=data) return response.json() def list_groups(self, **kwargs): """List all groups. :param kwargs: arbitrary search filters :returns: list of groups :rtype: list[:class:`marathon.models.group.MarathonGroup`] """ response = self._do_request('GET', '/v2/groups') groups = self._parse_response( response, MarathonGroup, is_list=True, resource_name='groups') for k, v in kwargs.items(): groups = [o for o in groups if getattr(o, k) == v] return groups def get_group(self, group_id): """Get a single group. :param str group_id: group ID :returns: group :rtype: :class:`marathon.models.group.MarathonGroup` """ response = self._do_request( 'GET', '/v2/groups/{group_id}'.format(group_id=group_id)) return self._parse_response(response, MarathonGroup) def update_group(self, group_id, group, force=False, minimal=True): """Update a group. Applies writable settings in `group` to `group_id` Note: this method can not be used to rename groups. :param str group_id: target group ID :param group: group settings :type group: :class:`marathon.models.group.MarathonGroup` :param bool force: apply even if a deployment is in progress :param bool minimal: ignore nulls and empty collections :returns: a dict containing the deployment id and version :rtype: dict """ # Changes won't take if version is set - blank it for convenience group.version = None params = {'force': force} data = group.to_json(minimal=minimal) response = self._do_request( 'PUT', '/v2/groups/{group_id}'.format(group_id=group_id), data=data, params=params) return response.json() def rollback_group(self, group_id, version, force=False): """Roll a group back to a previous version. :param str group_id: group ID :param str version: group version :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} response = self._do_request( 'PUT', '/v2/groups/{group_id}/versions/{version}'.format(group_id=group_id, version=version), params=params) return response.json() def delete_group(self, group_id, force=False): """Stop and destroy a group. :param str group_id: group ID :param bool force: apply even if a deployment is in progress :returns: a dict containing the deleted version :rtype: dict """ params = {'force': force} response = self._do_request( 'DELETE', '/v2/groups/{group_id}'.format(group_id=group_id), params=params) return response.json() def scale_group(self, group_id, scale_by): """Scale a group by a factor. :param str group_id: group ID :param int scale_by: factor to scale by :returns: a dict containing the deployment id and version :rtype: dict """ params = {'scaleBy': scale_by} response = self._do_request( 'PUT', '/v2/groups/{group_id}'.format(group_id=group_id), params=params) return response.json() def list_tasks(self, app_id=None, **kwargs): """List running tasks, optionally filtered by app_id. :param str app_id: if passed, only show tasks for this application :param kwargs: arbitrary search filters :returns: list of tasks :rtype: list[:class:`marathon.models.task.MarathonTask`] """ response = self._do_request('GET', '/v2/tasks') tasks = self._parse_response( response, MarathonTask, is_list=True, resource_name='tasks') if app_id: tasks = [ task for task in tasks if task.app_id.lstrip('/') == app_id.lstrip('/')] [setattr(t, 'app_id', app_id) for t in tasks if app_id and t.app_id is None] for k, v in kwargs.items(): tasks = [o for o in tasks if getattr(o, k) == v] return tasks def kill_given_tasks(self, task_ids, scale=False, force=None): """Kill a list of given tasks. :param list[str] task_ids: tasks to kill :param bool scale: if true, scale down the app by the number of tasks killed :param bool force: if true, ignore any current running deployments :return: True on success :rtype: bool """ params = {'scale': scale} if force is not None: params['force'] = force data = json.dumps({"ids": task_ids}) response = self._do_request( 'POST', '/v2/tasks/delete', params=params, data=data) return response == 200 def kill_tasks(self, app_id, scale=False, wipe=False, host=None, batch_size=0, batch_delay=0): """Kill all tasks belonging to app. :param str app_id: application ID :param bool scale: if true, scale down the app by the number of tasks killed :param str host: if provided, only terminate tasks on this Mesos slave :param int batch_size: if non-zero, terminate tasks in groups of this size :param int batch_delay: time (in seconds) to wait in between batched kills. If zero, automatically determine :returns: list of killed tasks :rtype: list[:class:`marathon.models.task.MarathonTask`] """ def batch(iterable, size): sourceiter = iter(iterable) while True: batchiter = itertools.islice(sourceiter, size) yield itertools.chain([next(batchiter)], batchiter) if batch_size == 0: # Terminate all at once params = {'scale': scale, 'wipe': wipe} if host: params['host'] = host response = self._do_request( 'DELETE', '/v2/apps/{app_id}/tasks'.format(app_id=app_id), params) # Marathon is inconsistent about what type of object it returns on the multi # task deletion endpoint, depending on the version of Marathon. See: # https://github.com/mesosphere/marathon/blob/06a6f763a75fb6d652b4f1660685ae234bd15387/src/main/scala/mesosphere/marathon/api/v2/AppTasksResource.scala#L88-L95 if "tasks" in response.json(): return self._parse_response(response, MarathonTask, is_list=True, resource_name='tasks') else: return response.json() else: # Terminate in batches tasks = self.list_tasks( app_id, host=host) if host else self.list_tasks(app_id) for tbatch in batch(tasks, batch_size): killed_tasks = [self.kill_task(app_id, t.id, scale=scale, wipe=wipe) for t in tbatch] # Pause until the tasks have been killed to avoid race # conditions killed_task_ids = set(t.id for t in killed_tasks) running_task_ids = killed_task_ids while killed_task_ids.intersection(running_task_ids): time.sleep(1) running_task_ids = set( t.id for t in self.get_app(app_id).tasks) if batch_delay == 0: # Pause until the replacement tasks are healthy desired_instances = self.get_app(app_id).instances running_instances = 0 while running_instances < desired_instances: time.sleep(1) running_instances = sum( t.started_at is None for t in self.get_app(app_id).tasks) else: time.sleep(batch_delay) return tasks def kill_task(self, app_id, task_id, scale=False, wipe=False): """Kill a task. :param str app_id: application ID :param str task_id: the task to kill :param bool scale: if true, scale down the app by one if the task exists :returns: the killed task :rtype: :class:`marathon.models.task.MarathonTask` """ params = {'scale': scale, 'wipe': wipe} response = self._do_request('DELETE', '/v2/apps/{app_id}/tasks/{task_id}' .format(app_id=app_id, task_id=task_id), params) # Marathon is inconsistent about what type of object it returns on the multi # task deletion endpoint, depending on the version of Marathon. See: # https://github.com/mesosphere/marathon/blob/06a6f763a75fb6d652b4f1660685ae234bd15387/src/main/scala/mesosphere/marathon/api/v2/AppTasksResource.scala#L88-L95 if "task" in response.json(): return self._parse_response(response, MarathonTask, is_list=False, resource_name='task') else: return response.json() def list_versions(self, app_id): """List the versions of an app. :param str app_id: application ID :returns: list of versions :rtype: list[str] """ response = self._do_request( 'GET', '/v2/apps/{app_id}/versions'.format(app_id=app_id)) return [version for version in response.json()['versions']] def get_version(self, app_id, version): """Get the configuration of an app at a specific version. :param str app_id: application ID :param str version: application version :return: application configuration :rtype: :class:`marathon.models.app.MarathonApp` """ response = self._do_request('GET', '/v2/apps/{app_id}/versions/{version}' .format(app_id=app_id, version=version)) return MarathonApp.from_json(response.json()) def list_event_subscriptions(self): """List the event subscriber callback URLs. :returns: list of callback URLs :rtype: list[str] """ response = self._do_request('GET', '/v2/eventSubscriptions') return [url for url in response.json()['callbackUrls']] def create_event_subscription(self, url): """Register a callback URL as an event subscriber. :param str url: callback URL :returns: the created event subscription :rtype: dict """ params = {'callbackUrl': url} response = self._do_request('POST', '/v2/eventSubscriptions', params) return response.json() def delete_event_subscription(self, url): """Deregister a callback URL as an event subscriber. :param str url: callback URL :returns: the deleted event subscription :rtype: dict """ params = {'callbackUrl': url} response = self._do_request('DELETE', '/v2/eventSubscriptions', params) return response.json() def list_deployments(self): """List all running deployments. :returns: list of deployments :rtype: list[:class:`marathon.models.deployment.MarathonDeployment`] """ response = self._do_request('GET', '/v2/deployments') return self._parse_response(response, MarathonDeployment, is_list=True) def list_queue(self): """List all the tasks queued up or waiting to be scheduled. :returns: list of queue items :rtype: list[:class:`marathon.models.queue.MarathonQueueItem`] """ response = self._do_request('GET', '/v2/queue') return self._parse_response(response, MarathonQueueItem, is_list=True, resource_name='queue') def delete_deployment(self, deployment_id, force=False): """Cancel a deployment. :param str deployment_id: deployment id :param bool force: if true, don't create a rollback deployment to restore the previous configuration :returns: a dict containing the deployment id and version (empty dict if force=True) :rtype: dict """ if force: params = {'force': True} self._do_request('DELETE', '/v2/deployments/{deployment}'.format( deployment=deployment_id), params=params) # Successful DELETE with ?force=true returns empty text (and status # code 202). Client code should poll until deployment is removed. return {} else: response = self._do_request( 'DELETE', '/v2/deployments/{deployment}'.format(deployment=deployment_id)) return response.json() def get_info(self): """Get server configuration information. :returns: server config info :rtype: :class:`marathon.models.info.MarathonInfo` """ response = self._do_request('GET', '/v2/info') return self._parse_response(response, MarathonInfo) def get_leader(self): """Get the current marathon leader. :returns: leader endpoint :rtype: dict """ response = self._do_request('GET', '/v2/leader') return response.json() def delete_leader(self): """Causes the current leader to abdicate, triggers a new election. :returns: message saying leader abdicated :rtype: dict """ response = self._do_request('DELETE', '/v2/leader') return response.json() def ping(self): """Ping the Marathon server. :returns: the text response :rtype: str """ response = self._do_request('GET', '/ping') return response.text def get_metrics(self): """Get server metrics :returns: metrics dict :rtype: dict """ response = self._do_request('GET', '/metrics') return response.json() def event_stream(self): """Polls event bus using /v2/events :returns: iterator with events :rtype: iterator """ ef = EventFactory() for raw_message in self._do_sse_request('/v2/events'): try: _data = raw_message.decode('utf8').split(':', 1) if _data[0] == 'data': event_data = json.loads(_data[1].strip()) if 'eventType' not in event_data: raise MarathonError('Invalid event data received.') yield ef.process(event_data) except ValueError: raise MarathonError('Invalid event data received.') python-marathon-0.8.10/marathon/models/0000755000175000017500000000000013034033706016777 5ustar chuckchuckpython-marathon-0.8.10/marathon/models/task.py0000644000175000017500000001011013034014670020303 0ustar chuckchuckfrom datetime import datetime from .base import MarathonResource, MarathonObject class MarathonTask(MarathonResource): """Marathon Task resource. :param str app_id: application id :param health_check_results: health check results :type health_check_results: list[:class:`marathon.models.MarathonHealthCheckResult`] or list[dict] :param str host: mesos slave running the task :param str id: task id :param list[int] ports: allocated ports :param list[int] service_ports: ports exposed for load balancing :param str state: State of the task e.g. TASK_RUNNING :param str slave_id: Mesos slave id :param staged_at: when this task was staged :type staged_at: datetime or str :param started_at: when this task was started :type started_at: datetime or str :param str version: app version with which this task was started """ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' def __init__(self, app_id=None, health_check_results=None, host=None, id=None, ports=None, service_ports=None, slave_id=None, staged_at=None, started_at=None, version=None, ip_addresses=[], state=None, local_volumes=None): self.app_id = app_id self.health_check_results = health_check_results or [] self.health_check_results = [ hcr if isinstance( hcr, MarathonHealthCheckResult) else MarathonHealthCheckResult().from_json(hcr) for hcr in (health_check_results or []) if any(health_check_results) ] self.host = host self.id = id self.ports = ports or [] self.service_ports = service_ports or [] self.slave_id = slave_id self.staged_at = staged_at if (staged_at is None or isinstance(staged_at, datetime)) \ else datetime.strptime(staged_at, self.DATETIME_FORMAT) self.started_at = started_at if (started_at is None or isinstance(started_at, datetime)) \ else datetime.strptime(started_at, self.DATETIME_FORMAT) self.state = state self.version = version self.ip_addresses = [ ipaddr if isinstance( ip_addresses, MarathonIpAddress) else MarathonIpAddress().from_json(ipaddr) for ipaddr in (ip_addresses or [])] self.local_volumes = local_volumes or [] class MarathonIpAddress(MarathonObject): """ """ def __init__(self, ip_address=None, protocol=None): self.ip_address = ip_address self.protocol = protocol class MarathonHealthCheckResult(MarathonObject): """Marathon health check result. See https://mesosphere.github.io/marathon/docs/health-checks.html :param bool alive: boolean to determine task health :param int consecutive_failures: number of failed healthchecks in a row :param str first_success: first time when which healthcheck succeeded :param str last_failure: last time when which healthcheck failed :param str last_failure_cause: cause for last failure :param str last_success: last time when which healthcheck succeeded :param str task_id: task id :param str instance_id: instance id """ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' def __init__(self, alive=None, consecutive_failures=None, first_success=None, last_failure=None, last_success=None, task_id=None, last_failure_cause=None, instance_id=None): self.alive = alive self.consecutive_failures = consecutive_failures self.first_success = first_success if (first_success is None or isinstance(first_success, datetime)) \ else datetime.strptime(first_success, self.DATETIME_FORMAT) self.last_failure = last_failure if (last_failure is None or isinstance(last_failure, datetime)) \ else datetime.strptime(last_failure, self.DATETIME_FORMAT) self.last_success = last_success if (last_success is None or isinstance(last_success, datetime)) \ else datetime.strptime(last_success, self.DATETIME_FORMAT) self.task_id = task_id self.last_failure_cause = last_failure_cause self.instance_id = instance_id python-marathon-0.8.10/marathon/models/endpoint.py0000644000175000017500000000320512705243404021173 0ustar chuckchuckfrom .base import MarathonObject class MarathonEndpoint(MarathonObject): """Marathon Endpoint helper object for service discovery. It describes a single port mapping for a running task. :param str app_id: application id :param str host: mesos slave running the task :param str task_id: task id :param int service_port: application service port :param int task_port: port allocated on the slave """ def __repr__(self): return "{clazz}::{app_id}::{service_port}::{task_id}::{task_port}".format( clazz=self.__class__.__name__, app_id=self.app_id, service_port=self.service_port, task_id=self.task_id, task_port=self.task_port ) def __init__(self, app_id=None, service_port=None, host=None, task_id=None, task_port=None): self.app_id = app_id self.service_port = service_port self.host = host self.task_id = task_id self.task_port = task_port @classmethod def from_tasks(cls, tasks): """Construct a list of MarathonEndpoints from a list of tasks. :param list[:class:`marathon.models.MarathonTask`] tasks: list of tasks to parse :rtype: list[:class:`MarathonEndpoint`] """ endpoints = [ [ MarathonEndpoint(task.app_id, task.service_ports[ port_index], task.host, task.id, port) for port_index, port in enumerate(task.ports) ] for task in tasks ] # Flatten result return [item for sublist in endpoints for item in sublist] python-marathon-0.8.10/marathon/models/info.py0000644000175000017500000001346113002517605020311 0ustar chuckchuckfrom .base import MarathonObject, MarathonResource class MarathonInfo(MarathonResource): """Marathon Info. See: https://mesosphere.github.io/marathon/docs/rest-api.html#get-v2-info Also: https://mesosphere.github.io/marathon/docs/generated/api.html#v2_info_get :param str framework_id: :param str leader: :param marathon_config: :type marathon_config: :class:`marathon.models.info.MarathonConfig` or dict :param str name: :param str version: :param zookeeper_config: :type zookeeper_config: :class:`marathon.models.info.MarathonZooKeeperConfig` or dict :param http_config: :type http_config: :class:`marathon.models.info.MarathonHttpConfig` or dict :param event_subscriber: :type event_subscriber: :class`marathon.models.info.MarathonEventSubscriber` or dict :param bool elected: :param str buildref: """ def __init__(self, event_subscriber=None, framework_id=None, http_config=None, leader=None, marathon_config=None, name=None, version=None, elected=None, zookeeper_config=None, buildref=None): if isinstance(event_subscriber, MarathonEventSubscriber): self.event_subscriber = event_subscriber elif event_subscriber is not None: self.event_subscriber = MarathonEventSubscriber().from_json( event_subscriber) else: self.event_subscriber = None self.framework_id = framework_id self.http_config = http_config if isinstance(http_config, MarathonHttpConfig) \ else MarathonHttpConfig().from_json(http_config) self.leader = leader self.marathon_config = marathon_config if isinstance(marathon_config, MarathonConfig) \ else MarathonConfig().from_json(marathon_config) self.name = name self.version = version self.elected = elected self.zookeeper_config = zookeeper_config if isinstance(zookeeper_config, MarathonZooKeeperConfig) \ else MarathonZooKeeperConfig().from_json(zookeeper_config) self.buildref = buildref class MarathonConfig(MarathonObject): """Marathon config resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/info :param bool checkpoint: :param str executor: :param int failover_timeout: :param type features: Undocumented object :param str framework_name: :param bool ha: :param str hostname: :param int leader_proxy_connection_timeout_ms: :param int leader_proxy_read_timeout_ms: :param int local_port_min: :param int local_port_max: :param str master: :param str mesos_leader_ui_url: :param str mesos_role: :param str mesos_user: :param str webui_url: :param int reconciliation_initial_delay: :param int reconciliation_interval: :param int task_launch_timeout: :param int task_reservation_timeout: :param int marathon_store_timeout: """ def __init__(self, checkpoint=None, executor=None, failover_timeout=None, framework_name=None, ha=None, hostname=None, leader_proxy_connection_timeout_ms=None, leader_proxy_read_timeout_ms=None, local_port_min=None, local_port_max=None, master=None, mesos_leader_ui_url=None, mesos_role=None, mesos_user=None, webui_url=None, reconciliation_initial_delay=None, reconciliation_interval=None, task_launch_timeout=None, marathon_store_timeout=None, task_reservation_timeout=None, features=None): self.checkpoint = checkpoint self.executor = executor self.failover_timeout = failover_timeout self.features = features self.ha = ha self.hostname = hostname self.local_port_min = local_port_min self.local_port_max = local_port_max self.master = master self.mesos_leader_ui_url = mesos_leader_ui_url self.mesos_role = mesos_role self.mesos_user = mesos_user self.webui_url = webui_url self.reconciliation_initial_delay = reconciliation_initial_delay self.reconciliation_interval = reconciliation_interval self.task_launch_timeout = task_launch_timeout self.task_reservation_timeout = task_reservation_timeout self.marathon_store_timeout = marathon_store_timeout class MarathonZooKeeperConfig(MarathonObject): """Marathon zookeeper config resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/info :param str zk: :param dict zk_future_timeout: :param str zk_hosts: :param str zk_max_versions: :param str zk_path: :param str zk_session_timeout: :param str zk_state: :param int zk_timeout: """ def __init__(self, zk=None, zk_future_timeout=None, zk_hosts=None, zk_max_versions=None, zk_path=None, zk_session_timeout=None, zk_state=None, zk_timeout=None): self.zk = zk self.zk_future_timeout = zk_future_timeout self.zk_hosts = zk_hosts self.zk_path = zk_path self.zk_state = zk_state self.zk_timeout = zk_timeout class MarathonHttpConfig(MarathonObject): """Marathon http config resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/info :param str assets_path: :param int http_port: :param int https_port: """ def __init__(self, assets_path=None, http_port=None, https_port=None): self.assets_path = assets_path self.http_port = http_port self.https_port = https_port class MarathonEventSubscriber(MarathonObject): """Marathon event subscriber resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#get-/v2/info :param str type: :param list[str] http_endpoints: """ def __init__(self, type=None, http_endpoints=None): self.type = type self.http_endpoints = http_endpoints python-marathon-0.8.10/marathon/models/base.py0000644000175000017500000000657113012736745020305 0ustar chuckchuckimport json import re from marathon.util import to_camel_case, to_snake_case, MarathonJsonEncoder, MarathonMinimalJsonEncoder class MarathonObject(object): """Base Marathon object.""" def __repr__(self): return "{clazz}::{obj}".format(clazz=self.__class__.__name__, obj=self.to_json(minimal=False)) def __eq__(self, other): return self.__dict__ == other.__dict__ def json_repr(self, minimal=False): """Construct a JSON-friendly representation of the object. :param bool minimal: Construct a minimal representation of the object (ignore nulls and empty collections) :rtype: dict """ if minimal: return {to_camel_case(k): v for k, v in vars(self).items() if (v or v is False or v == 0)} else: return {to_camel_case(k): v for k, v in vars(self).items()} @classmethod def from_json(cls, attributes): """Construct an object from a parsed response. :param dict attributes: object attributes from parsed response """ return cls(**{to_snake_case(k): v for k, v in attributes.items()}) def to_json(self, minimal=True): """Encode an object as a JSON string. :param bool minimal: Construct a minimal representation of the object (ignore nulls and empty collections) :rtype: str """ if minimal: return json.dumps(self.json_repr(minimal=True), cls=MarathonMinimalJsonEncoder, sort_keys=True) else: return json.dumps(self.json_repr(), cls=MarathonJsonEncoder, sort_keys=True) class MarathonResource(MarathonObject): """Base Marathon resource.""" def __repr__(self): if 'id' in list(vars(self).keys()): return "{clazz}::{id}".format(clazz=self.__class__.__name__, id=self.id) else: return "{clazz}::{obj}".format(clazz=self.__class__.__name__, obj=self.to_json()) def __eq__(self, other): return self.__dict__ == other.__dict__ def __str__(self): return "{clazz}::".format(clazz=self.__class__.__name__) + str(self.__dict__) # See: # https://github.com/mesosphere/marathon/blob/2a9d1d20ec2f1cfcc49fbb1c0e7348b26418ef38/src/main/scala/mesosphere/marathon/api/ModelValidation.scala#L224 ID_PATTERN = re.compile( '^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])|(\\.|\\.\\.)$') def assert_valid_path(path): """Checks if a path is a correct format that Marathon expects. Raises ValueError if not valid. :param str path: The app id. :rtype: str """ if path is None: return # As seen in: # https://github.com/mesosphere/marathon/blob/0c11661ca2f259f8a903d114ef79023649a6f04b/src/main/scala/mesosphere/marathon/state/PathId.scala#L71 for id in filter(None, path.strip('/').split('/')): if not ID_PATTERN.match(id): raise ValueError( 'invalid path (allowed: lowercase letters, digits, hyphen, "/", ".", ".."): %r' % path) return path def assert_valid_id(id): """Checks if an id is the correct format that Marathon expects. Raises ValueError if not valid. :param str id: App or group id. :rtype: str """ if id is None: return if not ID_PATTERN.match(id.strip('/')): raise ValueError( 'invalid id (allowed: lowercase letters, digits, hyphen, ".", ".."): %r' % id) return id python-marathon-0.8.10/marathon/models/__init__.py0000644000175000017500000000070112706225240021107 0ustar chuckchuckfrom .app import MarathonApp, MarathonHealthCheck from .base import MarathonResource, MarathonObject from .constraint import MarathonConstraint from .deployment import MarathonDeployment, MarathonDeploymentAction, MarathonDeploymentStep from .endpoint import MarathonEndpoint from .group import MarathonGroup from .info import MarathonInfo, MarathonConfig, MarathonZooKeeperConfig from .queue import MarathonQueueItem from .task import MarathonTask python-marathon-0.8.10/marathon/models/app.py0000644000175000017500000005451613034033563020145 0ustar chuckchuckfrom datetime import datetime from ..exceptions import InvalidChoiceError from .base import MarathonResource, MarathonObject, assert_valid_path from .constraint import MarathonConstraint from .container import MarathonContainer from .deployment import MarathonDeployment from .task import MarathonTask class MarathonApp(MarathonResource): """Marathon Application resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#post-/v2/apps :param list[str] accepted_resource_roles: a list of resource roles (the resource offer must contain at least one of these for the app to be launched on that host) :param list[str] args: args form of the command to run :param int backoff_factor: multiplier for subsequent backoff :param int backoff_seconds: base time, in seconds, for exponential backoff :param str cmd: cmd form of the command to run :param constraints: placement constraints :type constraints: list[:class:`marathon.models.constraint.MarathonConstraint`] or list[tuple] :param container: container info :type container: :class:`marathon.models.container.MarathonContainer` or dict :param float cpus: cpus required per instance :param list[str] dependencies: services (app IDs) on which this app depends :param int disk: disk required per instance :param deployments: (read-only) currently running deployments that affect this app :type deployments: list[:class:`marathon.models.deployment.MarathonDeployment`] :param dict env: env vars :param str executor: executor :param int gpus: gpus required per instance :param health_checks: health checks :type health_checks: list[:class:`marathon.models.MarathonHealthCheck`] or list[dict] :param str id: app id :param int instances: instances :param last_task_failure: last task failure :type last_task_failure: :class:`marathon.models.app.MarathonTaskFailure` or dict :param float mem: memory (in MB) required per instance :param dict secrets: A map with named secret declarations. :type port_definitions: list[:class:`marathon.models.app.PortDefinitions`] or list[dict] :param list[int] ports: ports :param bool require_ports: require the specified `ports` to be available in the resource offer :param list[str] store_urls: store URLs :param float task_rate_limit: (Removed in Marathon 0.7.0) maximum number of tasks launched per second :param tasks: (read-only) tasks :type tasks: list[:class:`marathon.models.task.MarathonTask`] :param int tasks_running: (read-only) the number of running tasks :param int tasks_staged: (read-only) the number of staged tasks :param int tasks_healthy: (read-only) the number of healthy tasks :param int tasks_unhealthy: (read-only) the number of unhealthy tasks :param upgrade_strategy: strategy by which app instances are replaced during a deployment :type upgrade_strategy: :class:`marathon.models.app.MarathonUpgradeStrategy` or dict :param list[str] uris: uris :param str user: user :param str version: version id :param version_info: time of last scaling, last config change :type version_info: :class:`marathon.models.app.MarathonAppVersionInfo` or dict :param task_stats: task statistics :type task_stats: :class:`marathon.models.app.MarathonTaskStats` or dict :param dict labels :type readiness_checks: list[:class:`marathon.models.app.ReadinessCheck`] or list[dict] :type residency: :class:`marathon.models.app.Residency` or dict :param int task_kill_grace_period_seconds: Configures the termination signal escalation behavior of executors when stopping tasks. :param list[dict] unreachable_strategy: Handling for unreachable instances. :param str kill_selection: Defines which instance should be killed first in case of e.g. rescaling. """ UPDATE_OK_ATTRIBUTES = [ 'args', 'backoff_factor', 'backoff_seconds', 'cmd', 'constraints', 'container', 'cpus', 'dependencies', 'disk', 'env', 'executor', 'gpus', 'health_checks', 'instances', 'kill_selection', 'labels', 'max_launch_delay_seconds', 'mem', 'ports', 'require_ports', 'store_urls', 'task_rate_limit', 'upgrade_strategy', 'unreachable_strategy', 'uris', 'user', 'version' ] """List of attributes which may be updated/changed after app creation""" CREATE_ONLY_ATTRIBUTES = ['id', 'accepted_resource_roles'] """List of attributes that should only be passed on creation""" READ_ONLY_ATTRIBUTES = [ 'deployments', 'tasks', 'tasks_running', 'tasks_staged', 'tasks_healthy', 'tasks_unhealthy'] """List of read-only attributes""" KILL_SELECTIONS = ["YoungestFirst", "OldestFirst"] def __init__(self, accepted_resource_roles=None, args=None, backoff_factor=None, backoff_seconds=None, cmd=None, constraints=None, container=None, cpus=None, dependencies=None, deployments=None, disk=None, env=None, executor=None, health_checks=None, id=None, instances=None, kill_selection=None, labels=None, last_task_failure=None, max_launch_delay_seconds=None, mem=None, ports=None, require_ports=None, store_urls=None, task_rate_limit=None, tasks=None, tasks_running=None, tasks_staged=None, tasks_healthy=None, task_kill_grace_period_seconds=None, tasks_unhealthy=None, upgrade_strategy=None, unreachable_strategy=None, uris=None, user=None, version=None, version_info=None, ip_address=None, fetch=None, task_stats=None, readiness_checks=None, readiness_check_results=None, secrets=None, port_definitions=None, residency=None, gpus=None): # self.args = args or [] self.accepted_resource_roles = accepted_resource_roles self.args = args # Marathon 0.7.0-RC1 throws a validation error if this is [] and cmd is passed: # "error": "AppDefinition must either contain a 'cmd' or a 'container'." self.backoff_factor = backoff_factor self.backoff_seconds = backoff_seconds self.cmd = cmd self.constraints = [ c if isinstance(c, MarathonConstraint) else MarathonConstraint(*c) for c in (constraints or []) ] self.container = container if (isinstance(container, MarathonContainer) or container is None) \ else MarathonContainer.from_json(container) self.cpus = cpus self.dependencies = dependencies or [] self.deployments = [ d if isinstance( d, MarathonDeployment) else MarathonDeployment().from_json(d) for d in (deployments or []) ] self.disk = disk self.env = env self.executor = executor self.gpus = gpus self.health_checks = health_checks or [] self.health_checks = [ hc if isinstance( hc, MarathonHealthCheck) else MarathonHealthCheck().from_json(hc) for hc in (health_checks or []) ] self.id = assert_valid_path(id) self.instances = instances if kill_selection and kill_selection not in self.KILL_SELECTIONS: raise InvalidChoiceError( 'kill_selection', kill_selection, self.KILL_SELECTIONS) self.kill_selection = kill_selection self.labels = labels or {} self.last_task_failure = last_task_failure if (isinstance(last_task_failure, MarathonTaskFailure) or last_task_failure is None) \ else MarathonTaskFailure.from_json(last_task_failure) self.max_launch_delay_seconds = max_launch_delay_seconds self.mem = mem self.ports = ports or [] self.port_definitions = [ pd if isinstance( pd, PortDefinition) else PortDefinition.from_json(pd) for pd in (port_definitions or []) ] self.readiness_checks = [ rc if isinstance( rc, ReadinessCheck) else ReadinessCheck().from_json(rc) for rc in (readiness_checks or []) ] self.readiness_check_results = readiness_check_results or [] self.residency = residency self.require_ports = require_ports self.secrets = secrets or {} for k, s in self.secrets.items(): if not isinstance(s, Secret): self.secrets[k] = Secret().from_json(s) self.store_urls = store_urls or [] self.task_rate_limit = task_rate_limit self.tasks = [ t if isinstance(t, MarathonTask) else MarathonTask().from_json(t) for t in (tasks or []) ] self.tasks_running = tasks_running self.tasks_staged = tasks_staged self.tasks_healthy = tasks_healthy self.task_kill_grace_period_seconds = task_kill_grace_period_seconds self.tasks_unhealthy = tasks_unhealthy self.upgrade_strategy = upgrade_strategy if (isinstance(upgrade_strategy, MarathonUpgradeStrategy) or upgrade_strategy is None) \ else MarathonUpgradeStrategy.from_json(upgrade_strategy) self.unreachable_strategy = unreachable_strategy \ if (isinstance(unreachable_strategy, MarathonUnreachableStrategy) or unreachable_strategy is None) \ else MarathonUnreachableStrategy.from_json(unreachable_strategy) self.uris = uris or [] self.fetch = fetch or [] self.user = user self.version = version self.version_info = version_info if (isinstance(version_info, MarathonAppVersionInfo) or version_info is None) \ else MarathonAppVersionInfo.from_json(version_info) self.task_stats = task_stats if (isinstance(task_stats, MarathonTaskStats) or task_stats is None) \ else MarathonTaskStats.from_json(task_stats) class MarathonHealthCheck(MarathonObject): """Marathon health check. See https://mesosphere.github.io/marathon/docs/health-checks.html :param str command: health check command (if protocol == 'COMMAND') :param int grace_period_seconds: how long to ignore health check failures on initial task launch (before first healthy status) :param int interval_seconds: how long to wait between health checks :param int max_consecutive_failures: max number of consecutive failures before the task should be killed :param str path: health check target path (if protocol == 'HTTP') :param int port_index: target port as indexed in app's `ports` array :param str protocol: health check protocol ('HTTP', 'TCP', or 'COMMAND') :param int timeout_seconds: how long before a waiting health check is considered failed :param bool ignore_http1xx: Ignore HTTP informational status codes 100 to 199. :param dict kwargs: additional arguments for forward compatibility """ def __init__(self, command=None, grace_period_seconds=None, interval_seconds=None, max_consecutive_failures=None, path=None, port_index=None, protocol=None, timeout_seconds=None, ignore_http1xx=None, **kwargs): self.command = command self.grace_period_seconds = grace_period_seconds self.interval_seconds = interval_seconds self.max_consecutive_failures = max_consecutive_failures self.path = path self.port_index = port_index self.protocol = protocol self.timeout_seconds = timeout_seconds self.ignore_http1xx = ignore_http1xx # additional not previously known healthcheck attributes for k, v in kwargs.items(): setattr(self, k, v) class MarathonTaskFailure(MarathonObject): """Marathon Task Failure. :param str app_id: application id :param str host: mesos slave running the task :param str message: error message :param str task_id: task id :param str instance_id: instance id :param str state: task state :param timestamp: when this task failed :type timestamp: datetime or str :param str version: app version with which this task was started """ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' def __init__(self, app_id=None, host=None, message=None, task_id=None, instance_id=None, slave_id=None, state=None, timestamp=None, version=None): self.app_id = app_id self.host = host self.message = message self.task_id = task_id self.instance_id = instance_id self.slave_id = slave_id self.state = state self.timestamp = timestamp if (timestamp is None or isinstance(timestamp, datetime)) \ else datetime.strptime(timestamp, self.DATETIME_FORMAT) self.version = version class MarathonUpgradeStrategy(MarathonObject): """Marathon health check. See https://mesosphere.github.io/marathon/docs/health-checks.html :param float minimum_health_capacity: minimum % of instances kept healthy on deploy """ def __init__(self, maximum_over_capacity=None, minimum_health_capacity=None): self.maximum_over_capacity = maximum_over_capacity self.minimum_health_capacity = minimum_health_capacity class MarathonUnreachableStrategy(MarathonObject): """Marathon unreachable Strategy. Define handling for unreachable instances. Given `unreachable_inactive_after_seconds = 60` and `unreachable_expunge_after = 120`, an instance will be expunged if it has been unreachable for more than 120 seconds or a second instance is started if it has been unreachable for more than 60 seconds.", See https://mesosphere.github.io/marathon/docs/? :param int unreachable_inactive_after_seconds: time an instance is unreachable for in seconds before marked as inactive. :param int unreachable_expunge_after_seconds: time an instance is unreachable for in seconds before expunged. :param int inactive_after_seconds :param int expunge_after_seconds """ def __init__(self, unreachable_inactive_after_seconds=None, unreachable_expunge_after_seconds=None, inactive_after_seconds=None, expunge_after_seconds=None): self.unreachable_inactive_after_seconds = unreachable_inactive_after_seconds self.unreachable_expunge_after_seconds = unreachable_expunge_after_seconds self.inactive_after_seconds = inactive_after_seconds self.expunge_after_seconds = expunge_after_seconds class MarathonAppVersionInfo(MarathonObject): """Marathon App version info. See release notes for Marathon v0.11.0 https://github.com/mesosphere/marathon/releases/tag/v0.11.0 :param str app_id: application id :param str host: mesos slave running the task """ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' def __init__(self, last_scaling_at=None, last_config_change_at=None): self.last_scaling_at = self._to_datetime(last_scaling_at) self.last_config_change_at = self._to_datetime(last_config_change_at) def _to_datetime(self, timestamp): if (timestamp is None or isinstance(timestamp, datetime)): return timestamp else: return datetime.strptime(timestamp, self.DATETIME_FORMAT) class MarathonTaskStats(MarathonObject): """Marathon task statistics See https://mesosphere.github.io/marathon/docs/rest-api.html#taskstats-object-v0-11 :param started_after_last_scaling: contains statistics about all tasks that were started after the last scaling or restart operation. :type started_after_last_scaling: :class:`marathon.models.app.MarathonTaskStatsType` or dict :param with_latest_config: contains statistics about all tasks that run with the same config as the latest app version. :type with_latest_config: :class:`marathon.models.app.MarathonTaskStatsType` or dict :param with_outdated_config: contains statistics about all tasks that were started before the last config change which was not simply a restart or scaling operation. :type with_outdated_config: :class:`marathon.models.app.MarathonTaskStatsType` or dict :param total_summary: contains statistics about all tasks. :type total_summary: :class:`marathon.models.app.MarathonTaskStatsType` or dict """ def __init__(self, started_after_last_scaling=None, with_latest_config=None, with_outdated_config=None, total_summary=None): self.started_after_last_scaling = started_after_last_scaling if \ (isinstance(started_after_last_scaling, MarathonTaskStatsType) or started_after_last_scaling is None) \ else MarathonTaskStatsType.from_json(started_after_last_scaling) self.with_latest_config = with_latest_config if \ (isinstance(with_latest_config, MarathonTaskStatsType) or with_latest_config is None) \ else MarathonTaskStatsType.from_json(with_latest_config) self.with_outdated_config = with_outdated_config if \ (isinstance(with_outdated_config, MarathonTaskStatsType) or with_outdated_config is None) \ else MarathonTaskStatsType.from_json(with_outdated_config) self.total_summary = total_summary if \ (isinstance(total_summary, MarathonTaskStatsType) or total_summary is None) \ else MarathonTaskStatsType.from_json(total_summary) class MarathonTaskStatsType(MarathonObject): """Marathon app task stats :param stats: stast about app tasks :type stats: :class:`marathon.models.app.MarathonTaskStatsStats` or dict """ def __init__(self, stats=None): self.stats = stats if (isinstance(stats, MarathonTaskStatsStats) or stats is None)\ else MarathonTaskStatsStats.from_json(stats) class MarathonTaskStatsStats(MarathonObject): """Marathon app task stats :param counts: app task count breakdown :type counts: :class:`marathon.models.app.MarathonTaskStatsCounts` or dict :param life_time: app task life time stats :type life_time: :class:`marathon.models.app.MarathonTaskStatsLifeTime` or dict """ def __init__(self, counts=None, life_time=None): self.counts = counts if (isinstance(counts, MarathonTaskStatsCounts) or counts is None)\ else MarathonTaskStatsCounts.from_json(counts) self.life_time = life_time if (isinstance(life_time, MarathonTaskStatsLifeTime) or life_time is None)\ else MarathonTaskStatsLifeTime.from_json(life_time) class MarathonTaskStatsCounts(MarathonObject): """Marathon app task counts Equivalent to tasksStaged, tasksRunning, tasksHealthy, tasksUnhealthy. :param int staged: Staged task count :param int running: Running task count :param int healthy: Healthy task count :param int unhealthy: unhealthy task count """ def __init__(self, staged=None, running=None, healthy=None, unhealthy=None): self.staged = staged self.running = running self.healthy = healthy self.unhealthy = unhealthy class MarathonTaskStatsLifeTime(MarathonObject): """Marathon app life time statistics Measured from `"startedAt"` (timestamp of the Mesos TASK_RUNNING status update) of each running task until now :param float average_seconds: Average seconds :param float median_seconds: Median seconds """ def __init__(self, average_seconds=None, median_seconds=None): self.average_seconds = average_seconds self.median_seconds = median_seconds class ReadinessCheck(MarathonObject): """Marathon readiness check: https://mesosphere.github.io/marathon/docs/readiness-checks.html :param string name (Optional. Default: "readinessCheck"): The name used to identify this readiness check. :param string protocol (Optional. Default: "HTTP"): Protocol of the requests to be performed. Either HTTP or HTTPS. :param string path (Optional. Default: "/"): Path to the endpoint the task exposes to provide readiness status. Example: /path/to/readiness. :param string port_name (Optional. Default: "http-api"): Name of the port to query as described in the portDefinitions. Example: http-api. :param int interval_seconds (Optional. Default: 30 seconds): Number of seconds to wait between readiness checks. :param int timeout_seconds (Optional. Default: 10 seconds): Number of seconds after which a readiness check times out, regardless of the response. This value must be smaller than interval_seconds. :param list http_status_codes_for_ready (Optional. Default: [200]): The HTTP/HTTPS status code to treat as ready. :param bool preserve_last_response (Optional. Default: false): If true, the last readiness check response will be preserved and exposed in the API as part of a deployment. """ def __init__(self, name=None, protocol=None, path=None, port_name=None, interval_seconds=None, http_status_codes_for_ready=None, preserve_last_response=None, timeout_seconds=None): self.name = name self.protocol = protocol self.path = path self.port_name = port_name self.interval_seconds = interval_seconds self.http_status_codes_for_ready = http_status_codes_for_ready self.preserve_last_response = preserve_last_response self.timeout_seconds = timeout_seconds class PortDefinition(MarathonObject): """Marathon port definitions: https://mesosphere.github.io/marathon/docs/ports.html :param int port: The port :param string protocol: tcp or udp :param string name: (optional) the name of the port :param dict labels: undocumented """ def __init__(self, port=None, protocol=None, name=None, labels=None): self.port = port self.protocol = protocol self.name = name self.labels = labels class Residency(MarathonObject): """Declares how "resident" an app is: https://mesosphere.github.io/marathon/docs/persistent-volumes.html :param int relaunch_escalation_timeout_seconds: How long marathon will try to relaunch where the volumes is, defaults to 3600 :param string task_lost_behavior: What to do after a TASK_LOST. See the official Marathon docs for options """ def __init__(self, relaunch_escalation_timeout_seconds=None, task_lost_behavior=None): self.relaunch_escalation_timeout_seconds = relaunch_escalation_timeout_seconds self.task_lost_behavior = task_lost_behavior class Secret(MarathonObject): """Declares marathon secret object. :param str source: The source of the secret's value. The format depends on the secret store used by Mesos. """ def __init__(self, source=None): self.source = source python-marathon-0.8.10/marathon/models/events.py0000644000175000017500000001202312761063330020655 0ustar chuckchuck""" This module is used to translate Events from Marathon's EventBus system. See: https://mesosphere.github.io/marathon/docs/event-bus.html """ from marathon.models.base import MarathonObject from marathon.models.app import MarathonHealthCheck from marathon.models.deployment import MarathonDeploymentPlan from marathon.exceptions import MarathonError class MarathonEvent(MarathonObject): """ The MarathonEvent base class handles the translation of Event objects sent by the Marathon server into library MarathonObjects. """ KNOWN_ATTRIBUTES = [] attribute_name_to_marathon_object = { # Allows embedding of MarathonObjects inside events. 'health_check': MarathonHealthCheck, 'plan': MarathonDeploymentPlan } def __init__(self, event_type, timestamp, **kwargs): self.event_type = event_type # All events have these two attributes self.timestamp = timestamp for attribute in self.KNOWN_ATTRIBUTES: self._set(attribute, kwargs.get(attribute)) def _set(self, attribute_name, attribute): if not attribute: return if attribute_name in self.attribute_name_to_marathon_object: clazz = self.attribute_name_to_marathon_object[attribute_name] attribute = clazz.from_json( attribute) # If this attribute already has a Marathon object instantiate it. setattr(self, attribute_name, attribute) class MarathonApiPostEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['client_ip', 'app_definition', 'uri'] class MarathonStatusUpdateEvent(MarathonEvent): KNOWN_ATTRIBUTES = [ 'slave_id', 'task_id', 'task_status', 'app_id', 'host', 'ports', 'version', 'message'] class MarathonFrameworkMessageEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['slave_id', 'executor_id', 'message'] class MarathonSubscribeEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['client_ip', 'callback_url'] class MarathonUnsubscribeEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['client_ip', 'callback_url'] class MarathonAddHealthCheckEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['app_id', 'health_check', 'version'] class MarathonRemoveHealthCheckEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['app_id', 'health_check'] class MarathonFailedHealthCheckEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['app_id', 'health_check', 'task_id'] class MarathonHealthStatusChangedEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['app_id', 'health_check', 'task_id', 'alive'] class MarathonGroupChangeSuccess(MarathonEvent): KNOWN_ATTRIBUTES = ['group_id', 'version'] class MarathonGroupChangeFailed(MarathonEvent): KNOWN_ATTRIBUTES = ['group_id', 'version', 'reason'] class MarathonDeploymentSuccess(MarathonEvent): KNOWN_ATTRIBUTES = ['id'] class MarathonDeploymentFailed(MarathonEvent): KNOWN_ATTRIBUTES = ['id'] class MarathonDeploymentInfo(MarathonEvent): KNOWN_ATTRIBUTES = ['plan'] class MarathonDeploymentStepSuccess(MarathonEvent): KNOWN_ATTRIBUTES = ['plan'] class MarathonDeploymentStepFailure(MarathonEvent): KNOWN_ATTRIBUTES = ['plan'] class MarathonEventStreamAttached(MarathonEvent): KNOWN_ATTRIBUTES = ['remote_address'] class MarathonEventStreamDetached(MarathonEvent): KNOWN_ATTRIBUTES = ['remote_address'] class MarathonUnhealthyTaskKillEvent(MarathonEvent): KNOWN_ATTRIBUTES = ['app_id', 'task_id', 'version', 'reason'] class EventFactory: """ Handle an event emitted from the Marathon EventBus See: https://mesosphere.github.io/marathon/docs/event-bus.html """ def __init__(self): pass event_to_class = { 'api_post_event': MarathonApiPostEvent, 'status_update_event': MarathonStatusUpdateEvent, 'framework_message_event': MarathonFrameworkMessageEvent, 'subscribe_event': MarathonSubscribeEvent, 'unsubscribe_event': MarathonUnsubscribeEvent, 'add_health_check_event': MarathonAddHealthCheckEvent, 'remove_health_check_event': MarathonRemoveHealthCheckEvent, 'failed_health_check_event': MarathonFailedHealthCheckEvent, 'health_status_changed_event': MarathonHealthStatusChangedEvent, 'unhealthy_task_kill_event': MarathonUnhealthyTaskKillEvent, 'group_change_success': MarathonGroupChangeSuccess, 'group_change_failed': MarathonGroupChangeFailed, 'deployment_success': MarathonDeploymentSuccess, 'deployment_failed': MarathonDeploymentFailed, 'deployment_info': MarathonDeploymentInfo, 'deployment_step_success': MarathonDeploymentStepSuccess, 'deployment_step_failure': MarathonDeploymentStepFailure, 'event_stream_attached': MarathonEventStreamAttached, 'event_stream_detached': MarathonEventStreamDetached, } def process(self, event): event_type = event['eventType'] if event_type in self.event_to_class: clazz = self.event_to_class[event_type] return clazz.from_json(event) else: raise MarathonError('Unknown event_type: {}, data: {}'.format(event_type, event)) python-marathon-0.8.10/marathon/models/deployment.py0000644000175000017500000000765013003450265021540 0ustar chuckchuckfrom .base import MarathonObject, MarathonResource class MarathonDeployment(MarathonResource): """Marathon Application resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#deployments https://mesosphere.github.io/marathon/docs/generated/api.html#v2_deployments_get :param list[str] affected_apps: list of affected app ids :param current_actions: current actions :type current_actions: list[:class:`marathon.models.deployment.MarathonDeploymentAction`] or list[dict] :param int current_step: current step :param str id: deployment id :param steps: deployment steps :type steps: list[:class:`marathon.models.deployment.MarathonDeploymentAction`] or list[dict] :param int total_steps: total number of steps :param str version: version id :param str affected_pods: list of strings """ def __init__(self, affected_apps=None, current_actions=None, current_step=None, id=None, steps=None, total_steps=None, version=None, affected_pods=None): self.affected_apps = affected_apps self.current_actions = [ a if isinstance( a, MarathonDeploymentAction) else MarathonDeploymentAction().from_json(a) for a in (current_actions or []) ] self.current_step = current_step self.id = id self.steps = [self.parse_deployment_step(step) for step in (steps or [])] self.total_steps = total_steps self.version = version self.affected_pods = affected_pods def parse_deployment_step(self, step): if step.__class__ == dict: # This is what Marathon 1.0.0 returns: steps return MarathonDeploymentStep().from_json(step) elif step.__class__ == list: # This is Marathon < 1.0.0 style, a list of actions return [s if isinstance(s, MarathonDeploymentAction) else MarathonDeploymentAction().from_json(s) for s in step] else: return step class MarathonDeploymentAction(MarathonObject): """Marathon Application resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#deployments :param str action: action :param str app: app id :param str apps: app id (see https://github.com/mesosphere/marathon/pull/802) :param type readiness_check_results: Undocumented """ def __init__(self, action=None, app=None, apps=None, type=None, readiness_check_results=None): self.action = action self.app = app self.apps = apps self.type = type # TODO: Remove builtin shadow self.readiness_check_results = readiness_check_results # TODO: The docs say this is called just "readinessChecks?" class MarathonDeploymentPlan(MarathonObject): def __init__(self, original=None, target=None, steps=None, id=None, version=None): self.original = MarathonDeploymentOriginalState.from_json(original) self.target = MarathonDeploymentTargetState.from_json(target) self.steps = [MarathonDeploymentStep.from_json(x) for x in steps] self.id = id self.version = version class MarathonDeploymentStep(MarathonObject): def __init__(self, actions=None): self.actions = [a if isinstance(a, MarathonDeploymentAction) else MarathonDeploymentAction.from_json(a) for a in (actions or [])] class MarathonDeploymentOriginalState(MarathonObject): def __init__(self, dependencies=None, apps=None, id=None, version=None, groups=None): self.apps = apps self.groups = groups self.id = id self.version = version self.dependencies = dependencies class MarathonDeploymentTargetState(MarathonObject): def __init__(self, groups=None, apps=None, dependencies=None, id=None, version=None): self.apps = apps self.groups = groups self.id = id self.version = version self.dependencies = dependencies python-marathon-0.8.10/marathon/models/container.py0000644000175000017500000001001013002517605021323 0ustar chuckchuckfrom ..exceptions import InvalidChoiceError from .base import MarathonObject class MarathonContainer(MarathonObject): """Marathon health check. See https://mesosphere.github.io/marathon/docs/native-docker.html :param docker: docker field (e.g., {"image": "mygroup/myimage"})' :type docker: :class:`marathon.models.container.MarathonDockerContainer` or dict :param str type: :param volumes: :type volumes: list[:class:`marathon.models.container.MarathonContainerVolume`] or list[dict] """ TYPES = ['DOCKER', 'MESOS'] """Valid container types""" def __init__(self, docker=None, type='DOCKER', volumes=None): if type not in self.TYPES: raise InvalidChoiceError('type', type, self.TYPES) self.type = type if docker: self.docker = docker if isinstance(docker, MarathonDockerContainer) \ else MarathonDockerContainer().from_json(docker) self.volumes = [ v if isinstance( v, MarathonContainerVolume) else MarathonContainerVolume().from_json(v) for v in (volumes or []) ] class MarathonDockerContainer(MarathonObject): """Docker options. See https://mesosphere.github.io/marathon/docs/native-docker.html :param str image: docker image :param str network: :param port_mappings: :type port_mappings: list[:class:`marathon.models.container.MarathonContainerPortMapping`] or list[dict] :param list[dict] parameters: :param bool privileged: run container in privileged mode :param bool force_pull_image: Force a docker pull before launching """ NETWORK_MODES = ['BRIDGE', 'HOST', 'NONE'] """Valid network modes""" def __init__(self, image=None, network='HOST', port_mappings=None, parameters=None, privileged=None, force_pull_image=None, **kwargs): self.image = image if network: if network not in self.NETWORK_MODES: raise InvalidChoiceError( 'network', network, self.NETWORK_MODES) self.network = network self.port_mappings = [ pm if isinstance( pm, MarathonContainerPortMapping) else MarathonContainerPortMapping().from_json(pm) for pm in (port_mappings or []) ] self.parameters = parameters or [] self.privileged = privileged or False self.force_pull_image = force_pull_image or False class MarathonContainerPortMapping(MarathonObject): """Container port mapping. See https://mesosphere.github.io/marathon/docs/native-docker.html :param str name: :param int container_port: :param int host_port: :param str protocol: :param object labels: """ PROTOCOLS = ['tcp', 'udp'] """Valid protocols""" def __init__(self, name=None, container_port=None, host_port=0, service_port=None, protocol='tcp', labels=None): self.name = name self.container_port = container_port self.host_port = host_port self.service_port = service_port if protocol not in self.PROTOCOLS: raise InvalidChoiceError('protocol', protocol, self.PROTOCOLS) self.protocol = protocol self.labels = labels class MarathonContainerVolume(MarathonObject): """Volume options. See https://mesosphere.github.io/marathon/docs/native-docker.html :param str container_path: container path :param str host_path: host path :param str mode: one of ['RO', 'RW'] :param object persistent: persistent volume options, should be of the form {'size': 1000} :param object external: external volume options """ MODES = ['RO', 'RW'] def __init__(self, container_path=None, host_path=None, mode='RW', persistent=None, external=None): self.container_path = container_path self.host_path = host_path if mode not in self.MODES: raise InvalidChoiceError('mode', mode, self.MODES) self.mode = mode self.persistent = persistent self.external = external python-marathon-0.8.10/marathon/models/constraint.py0000644000175000017500000000376412705243404021551 0ustar chuckchuckfrom ..exceptions import InvalidChoiceError from .base import MarathonObject class MarathonConstraint(MarathonObject): """Marathon placement constraint. See https://mesosphere.github.io/marathon/docs/constraints.html :param str field: constraint operator target :param str operator: must be one of [UNIQUE, CLUSTER, GROUP_BY, LIKE, UNLIKE] :param value: [optional] if `operator` is CLUSTER, constrain tasks to servers where `field` == `value`. If `operator` is GROUP_BY, place at most `value` tasks per group. If `operator` is `LIKE` or `UNLIKE`, filter servers using regexp. :type value: str, int, or None """ OPERATORS = ['UNIQUE', 'CLUSTER', 'GROUP_BY', 'LIKE', 'UNLIKE'] """Valid operators""" def __init__(self, field, operator, value=None): if operator not in self.OPERATORS: raise InvalidChoiceError('operator', operator, self.OPERATORS) self.field = field self.operator = operator self.value = value def __repr__(self): if self.value: template = "MarathonConstraint::{field}:{operator}:{value}" else: template = "MarathonConstraint::{field}:{operator}" return template.format(**self.__dict__) def json_repr(self, minimal=False): """Construct a JSON-friendly representation of the object. :param bool minimal: [ignored] :rtype: list """ if self.value: return [self.field, self.operator, self.value] else: return [self.field, self.operator] @classmethod def from_json(cls, obj): """Construct a MarathonConstraint from a parsed response. :param dict attributes: object attributes from parsed response :rtype: :class:`MarathonConstraint` """ if len(obj) == 2: (field, operator) = obj return cls(field, operator) if len(obj) > 2: (field, operator, value) = obj return cls(field, operator, value) python-marathon-0.8.10/marathon/models/queue.py0000644000175000017500000000326412705243404020504 0ustar chuckchuckfrom .base import MarathonResource from .app import MarathonApp class MarathonQueueItem(MarathonResource): """Marathon queue item. See: https://mesosphere.github.io/marathon/docs/rest-api.html#queue List all the tasks queued up or waiting to be scheduled. This is mainly used for troubleshooting and occurs when scaling changes are requested and the volume of scaling changes out paces the ability to schedule those tasks. In addition to the application in the queue, you see also the task count that needs to be started. If the task has a rate limit, then a delay to the start gets applied. You can see this delay for every application with the seconds to wait before the next launch will be tried. :param app: :type app: :class:`marathon.models.app.MarathonApp` or dict :param delay: queue item delay :type delay: :class:`marathon.models.app.MarathonQueueItemDelay` or dict :param bool overdue: """ def __init__(self, app=None, overdue=None, count=None, delay=None): self.app = app if isinstance( app, MarathonApp) else MarathonApp().from_json(app) self.overdue = overdue self.count = count self.delay = delay if isinstance( delay, MarathonQueueItemDelay) else MarathonQueueItemDelay().from_json(delay) class MarathonQueueItemDelay(MarathonResource): """Marathon queue item delay. :param int time_left_seconds: Seconds to wait before the next launch will be tried. :param bool overdue: Is the queue item overdue. """ def __init__(self, time_left_seconds=None, overdue=None): self.time_left_seconds = time_left_seconds self.overdue = overdue python-marathon-0.8.10/marathon/models/group.py0000644000175000017500000000250313034000526020477 0ustar chuckchuckfrom .base import MarathonResource, assert_valid_id from .app import MarathonApp class MarathonGroup(MarathonResource): """Marathon group resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#groups :param apps: :type apps: list[:class:`marathon.models.app.MarathonApp`] or list[dict] :param list[str] dependencies: :param groups: :type groups: list[:class:`marathon.models.group.MarathonGroup`] or list[dict] :param str id: :param pods: :type pods: list[:class:`marathon.models.pod.MarathonPod`] or list[dict] :param str version: """ def __init__(self, apps=None, dependencies=None, groups=None, id=None, pods=None, version=None): self.apps = [ a if isinstance(a, MarathonApp) else MarathonApp().from_json(a) for a in (apps or []) ] self.dependencies = dependencies or [] self.groups = [ g if isinstance(g, MarathonGroup) else MarathonGroup().from_json(g) for g in (groups or []) ] self.pods = [] # ToDo: Create class MarathonPod # self.pods = [ # p if isinstance(p, MarathonPod) else MarathonPod().from_json(p) # for p in (pods or []) # ] self.id = assert_valid_id(id) self.version = version python-marathon-0.8.10/setup.cfg0000644000175000017500000000013013034033706015516 0ustar chuckchuck[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0