Coder Social home page Coder Social logo

marathon-python's Introduction

marathon-python

Build Status

This is a Python library for interfacing with Marathon servers via Marathon's REST API.

Compatibility

  • For Marathon 1.9.x and 1.10.x, use at least 0.13.0
  • For Marathon 1.6.x, use at least 0.10.0
  • For Marathon 1.4.1, use at least 0.8.13
  • For Marathon 1.1.1, use at least 0.8.1
  • For all version changes, please see CHANGELOG.md

If you find a feature that is broken, please submit a PR that adds a test for it so it will be fixed and will continue to stay fixed as Marathon changes over time.

Just because this library is tested against a specific version of Marathon, doesn't necessarily mean that it supports every feature and API Marathon provides.

Installation

From PyPi (recommended)

pip install marathon

From GitHub

pip install -e [email protected]:thefactory/marathon-python.git#egg=marathon

From source

git clone [email protected]:thefactory/marathon-python
python marathon-python/setup.py install

Testing

marathon-python uses Travis to test the code against different versions of Marathon. You can run the tests locally on a Linux machine that has docker on it:

Running The Tests

make itests

Running The Tests Against a Specific Version of Marathon

MARATHONVERSION=v1.6.322 make itests

Documentation

API documentation is here.

Or you can build the documentation yourself:

pip install sphinx
pip install sphinx_rtd_theme
cd docs/
make html

The documentation will be in <project-root>/gh-pages/html:

open gh-pages/html/index.html

Basic Usage

Create a MarathonClient() instance pointing at your Marathon server(s):

>>> from marathon import MarathonClient
>>> c = MarathonClient('http://localhost:8080')

>>> # or multiple servers:
>>> c = MarathonClient(['http://host1:8080', 'http://host2:8080'])

Then try calling some methods:

>>> c.list_apps()
[MarathonApp::myapp1, MarathonApp::myapp2]
>>> from marathon.models import MarathonApp
>>> c.create_app('myapp3', MarathonApp(cmd='sleep 100', mem=16, cpus=1))
MarathonApp::myapp3
>>> app = c.get_app('myapp3')
>>> app.ports
[19671]
>>> app.mem = 32
>>> c.update_app('myapp3', app)
{'deploymentId': '83b215a6-4e26-4e44-9333-5c385eda6438', 'version': '2014-08-26T07:37:50.462Z'}
>>> c.get_app('myapp3').mem
32.0
>>> c.get_app('myapp3').instances
1
>>> c.scale_app('myapp3', instances=3)
{'deploymentId': '611b89e3-99f2-4d8a-afe1-ec0b83fdbb88', 'version': '2014-08-26T07:40:20.121Z'}
>>> c.get_app('myapp3').instances
3
>>> c.scale_app('myapp3', delta=-1)
{'deploymentId': '1081a99c-55e8-4404-907b-4a3697059848', 'version': '2014-08-26T07:43:30.232Z'}
>>> c.get_app('myapp3').instances
2
>>> c.list_tasks('myapp1')
[MarathonTask:myapp1-1398201790254]
>>> c.kill_tasks('myapp1', scale=True)
[MarathonTask:myapp1-1398201790254]
>>> c.list_tasks('myapp1')
[]

License

Open source under the MIT License. See LICENSE.

marathon-python's People

Contributors

ala-allunite avatar bergerx avatar daltonmatos avatar dilyn avatar diogommartins avatar elyast avatar evankrall avatar fengyehong avatar gabber12 avatar gisjedi avatar hlerebours avatar iandyh avatar jdewinne avatar jolynch avatar keshavdv avatar kevinschoon avatar mattrobenolt avatar mbabineau avatar migueleliasweb avatar missingcharacter avatar moonkev avatar nathanleiby avatar nhandler avatar oilbeater avatar protetore avatar rob-johnson avatar solarkennedy avatar stj avatar tanderegg avatar usmanm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

marathon-python's Issues

[Question] Passing parameters to request.get

Here in the company where I work we can only access the marathon api through a proxy, but sadly I cannot pass any extra parameter to requests.session.

A simple workaround would be to implement an additional argument that could be passed on the class initialization with extra parameters, something like:

class MarathonClient(object):

    """Client interface for the Marathon REST API."""

    def __init__(self, servers, username=None, password=None, timeout=10, requests_extra_args={}):
        #some ommited code
        self.requests_extra_args = requests_extra_args

     def _do_request(self, method, path, params=None, data=None):
        """Query Marathon server."""
        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }

        headers.update(self.requests_extra_args)

What do you think ? Does this worth a PR ?

New marathon application structure field

taskKillGracePeriodSeconds field was added to marathon application struct.
Documentation about it can be found here - https://github.com/mesosphere/marathon/blob/master/docs/docs/rest-api/mesosphere/marathon/api/v2/AppsResource_create.md

Also secrets field was added, but i can't find any documentation about it right now, its only mentioned in schema - https://github.com/mesosphere/marathon/blob/master/docs/docs/rest-api/public/api/v2/schema/AppDefinition.json

[Profiling] Humongous CPU with event_stream

I recently developed with the help of your client, which is quite awesome, a listener for marathon events using the event_stream() method.

The idea is simple, listen for the "deployment_step_success" event and execute some internal business code when this happens.

During the development/test phase I used a local marathon installation to provide the events to my code but when the code went live some problems started to happen.

The code seemed much slower than I expected and the CPU usage was ginormous.

After a quick profiling I discovered the problem was lying on the SSECLIENT lib that is used behind the scenes then I extracted the bit of code that actually listens for the events and performed another test.

from marathon import MarathonClient

marathon_client = MarathonClient(marathon_address)

for _event in marathon_client.event_stream():
    print("Got event : {}".format(_event.event_type))

OBS: The mesos/marathon cluster on production is around 60 apps with 200 tasks total

This simple code hangs about two minutes before outputs "Got event whatever" !

Htop output:
screenshot from 2016-08-22 12-02-14

Here's the cProfiling output:

17857859 function calls (16810521 primitive calls) in 425.454 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    143/1    0.007    0.000  425.454  425.454 {built-in method exec}
        1    0.001    0.001  425.454  425.454 events.py:1(<module>)
       22    0.001    0.000  425.326   19.333 client.py:696(event_stream)
       38    4.582    0.121  425.004   11.184 sseclient.py:58(__next__)
   522457    0.650    0.000  268.910    0.001 sseclient.py:52(_event_complete)
   522458    0.644    0.000  268.261    0.001 re.py:167(search)
   522464  266.073    0.001  266.073    0.001 {method 'search' of '_sre.SRE_Pattern' objects}
1567259/522420    0.770    0.000  151.506    0.000 {built-in method next}
   522420    0.696    0.000  151.198    0.000 utils.py:368(stream_decode_response_unicode)
   522420    0.279    0.000  149.246    0.000 models.py:672(generate)
   522420    0.975    0.000  148.967    0.000 response.py:336(stream)
   522420    3.639    0.000  146.838    0.000 response.py:276(read)
   522420    1.078    0.000  134.903    0.000 client.py:518(read)
   522425    0.731    0.000  133.973    0.000 {method 'read' of '_io._RawIOBase' objects}
   522420    0.656    0.000  133.094    0.000 client.py:549(readinto)
       85    0.000    0.000  132.453    1.558 socket.py:360(readinto)
       85  132.452    1.558  132.452    1.558 {method 'recv_into' of '_socket.socket' objects}
   522420    0.281    0.000  132.438    0.000 {method 'readinto' of '_io.BufferedReader' objects}
   522420    0.976    0.000    3.345    0.000 response.py:180(_init_decoder)

Besides "recv_into" which is too low level, the regex is executes half a million times which is responsible for the most time spent during the whole execution.

Any ideas of how to workaround this ?

Task.ip_addresses are not set properly

in MarathonTask

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=[]):
        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.version = version

ip_addresses are passed but not set to self.ip_addresses

marathon.exceptions.InvalidChoiceError: Invalid choice "tcp,udp" for param "protocol". Must be one of ['tcp', 'udp']

If you have a config with:

{
          "containerPort": 514,
          "hostPort": 1514,
          "protocol": "udp,tcp",
          "labels": {}
}

You got the error:

Unexpected error: <class 'marathon.exceptions.InvalidChoiceError'>
Traceback (most recent call last):
File "deployment.py", line 90, in <module>
main()
File "deployment.py", line 84, in main
deploy(service,appid,instances,marathon,down)
File "deployment.py", line 42, in deploy
for app in c.list_apps():
File "/usr/local/lib/python3.5/site-packages/marathon/client.py", line 183, in list_apps
response, MarathonApp, is_list=True, resource_name='apps')
File "/usr/local/lib/python3.5/site-packages/marathon/client.py", line 53, in _parse_response
return [clazz.from_json(resource) for resource in target]
File "/usr/local/lib/python3.5/site-packages/marathon/client.py", line 53, in <listcomp>
return [clazz.from_json(resource) for resource in target]
File "/usr/local/lib/python3.5/site-packages/marathon/models/base.py", line 35, in from_json
return cls(**
{to_snake_case(k): v for k, v in attributes.items()}
)
File "/usr/local/lib/python3.5/site-packages/marathon/models/app.py", line 107, in _init_
else MarathonContainer.from_json(container)
File "/usr/local/lib/python3.5/site-packages/marathon/models/base.py", line 35, in from_json
return cls(**
{to_snake_case(k): v for k, v in attributes.items()}
)
File "/usr/local/lib/python3.5/site-packages/marathon/models/container.py", line 26, in _init_
else MarathonDockerContainer().from_json(docker)
File "/usr/local/lib/python3.5/site-packages/marathon/models/base.py", line 35, in from_json
return cls(**
{to_snake_case(k): v for k, v in attributes.items()}
)
File "/usr/local/lib/python3.5/site-packages/marathon/models/container.py", line 63, in _init_
for pm in (port_mappings or [])
File "/usr/local/lib/python3.5/site-packages/marathon/models/container.py", line 63, in <listcomp>
for pm in (port_mappings or [])
File "/usr/local/lib/python3.5/site-packages/marathon/models/base.py", line 35, in from_json
return cls(**
{to_snake_case(k): v for k, v in attributes.items()}
)
File "/usr/local/lib/python3.5/site-packages/marathon/models/container.py", line 92, in _init_
raise InvalidChoiceError('protocol', protocol, self.PROTOCOLS)
marathon.exceptions.InvalidChoiceError: Invalid choice "tcp,udp" for param "protocol". Must be one of ['tcp', 'udp']

But according to the documentation is it possible (https://mesosphere.github.io/marathon/docs/ports.html)

protocol: Protocol specifies the internet protocol to use for a port (e.g. tcp, udp or udp,tcp for both). This is only necessary as part of a port mapping when using BRIDGE or USER mode networking with a Docker container.

when run list_apps ,has errors

from marathon import MarathonClient
c = MarathonClient('http://10.0.2.47:8090')
c.list_apps()

_Traceback (most recent call last):
File "", line 1, in
File "marathon/client.py", line 165, in list_apps
response, MarathonApp, is_list=True, resource_name='apps')
File "marathon/client.py", line 48, in parse_response
return [clazz.from_json(resource) for resource in target]
File "marathon/models/base.py", line 35, in from_json
return cls({to_snake_case(k): v for k, v in attributes.items()})
File "marathon/models/app.py", line 103, in init
else MarathonContainer.from_json(container)
File "marathon/models/base.py", line 35, in from_json
return cls(
{to_snake_case(k): v for k, v in attributes.items()})
File "marathon/models/container.py", line 26, in init
else MarathonDockerContainer().from_json(docker)
File "marathon/models/base.py", line 35, in from_json
return cls({to_snake_case(k): v for k, v in attributes.items()})
File "marathon/models/container.py", line 64, in init
for pm in (port_mappings or [])
File "marathon/models/base.py", line 35, in from_json
return cls(
{to_snake_case(k): v for k, v in attributes.items()})
TypeError: init() got an unexpected keyword argument 'name'

RuntimeError: maximum recursion depth exceeded in cmp when calling create_app

I am trying to create new app using library:

My json is following

{
    "id": "spark-history",
    "cmd": "/mnt/lib/spark/sbin/run-history-server.sh hdfs://stanley/spark-events $PORT",
    "cpus": 0.5,
    "mem": 1024,
    "env": {"MARATHON": "http://as-ha-1:8773,http://as-ha-2:8773,http://as-master:8773"},
    "constraints": [["hostname", "UNIQUE"]],
    "healthChecks": [{"path": "/", "portIndex": 0, "protocol": "HTTP"}],
    "ports": [5999],
    "upgradeStrategy": {"minimumHealthCapacity": 1},
    "instances": 1
}

my code to create such app is following:

cli = marathon.MarathonClient(['sample_address'])
mApp = marathon.MarathonApp.from_json(app_attr)
app = cli.create_app('sample_name', mApp)

I am testing it against marathon 0.9.1, request worked in version 0.6.15 (tough parsing response failed)

however in version 0.7.1 I am getting following exception:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-20-c21615dd7f2c> in <module>()
----> 1 app = cli.create_app(app_name, mApp)

/usr/local/lib/python2.7/site-packages/marathon/client.pyc in create_app(self, app_id, app)
    109         """
    110         app.id = app_id
--> 111         data = app.to_json()
    112         response = self._do_request('POST', '/v2/apps', data=data)
    113         if response.status_code == 201:

/usr/local/lib/python2.7/site-packages/marathon/models/base.pyc in to_json(self, minimal)
     39         """
     40         if minimal:
---> 41             return json.dumps(self.json_repr(minimal=True), cls=MarathonMinimalJsonEncoder, sort_keys=True)
     42         else:
     43             return json.dumps(self.json_repr(), cls=MarathonJsonEncoder, sort_keys=True)

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, sort_keys, **kw)
    248         check_circular=check_circular, allow_nan=allow_nan, indent=indent,
    249         separators=separators, encoding=encoding, default=default,
--> 250         sort_keys=sort_keys, **kw).encode(obj)
    251 
    252 

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in encode(self, o)
    207         chunks = self.iterencode(o, _one_shot=True)
    208         if not isinstance(chunks, (list, tuple)):
--> 209             chunks = list(chunks)
    210         return ''.join(chunks)
    211 

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in _iterencode(o, _current_indent_level)
    432                 yield chunk
    433         elif isinstance(o, dict):
--> 434             for chunk in _iterencode_dict(o, _current_indent_level):
    435                 yield chunk
    436         else:

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in _iterencode_dict(dct, _current_indent_level)
    406                 else:
    407                     chunks = _iterencode(value, _current_indent_level)
--> 408                 for chunk in chunks:
    409                     yield chunk
    410         if newline_indent is not None:

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in _iterencode_list(lst, _current_indent_level)
    330                 else:
    331                     chunks = _iterencode(value, _current_indent_level)
--> 332                 for chunk in chunks:
    333                     yield chunk
    334         if newline_indent is not None:

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in _iterencode(o, _current_indent_level)
    440                     raise ValueError("Circular reference detected")
    441                 markers[markerid] = o
--> 442             o = _default(o)
    443             for chunk in _iterencode(o, _current_indent_level):
    444                 yield chunk

/usr/local/lib/python2.7/site-packages/marathon/util.pyc in default(self, obj)
     34     def default(self, obj):
     35         if hasattr(obj, 'json_repr'):
---> 36             return self.default(obj.json_repr(minimal=True))
     37 
     38         if isinstance(obj, datetime.datetime):

/usr/local/lib/python2.7/site-packages/marathon/util.pyc in default(self, obj)
     43                 return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
     44             except AttributeError:
---> 45                 return [self.default(e) for e in obj if (e or e == False)]
     46 
     47         return obj

/usr/local/lib/python2.7/site-packages/marathon/util.pyc in default(self, obj)
     43                 return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
     44             except AttributeError:
---> 45                 return [self.default(e) for e in obj if (e or e == False)]
     46 
     47         return obj

...

# here the infinite loop starts and finally (above few lines repeats)

...

/usr/local/lib/python2.7/site-packages/marathon/util.pyc in default(self, obj)
     43                 return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
     44             except AttributeError:
---> 45                 return [self.default(e) for e in obj if (e or e == False)]
     46 
     47         return obj

/usr/local/lib/python2.7/site-packages/marathon/util.pyc in default(self, obj)
     39             return obj.isoformat()
     40 
---> 41         if isinstance(obj, collections.Iterable) and not isinstance(obj, str):
     42             try:
     43                 return {k: self.default(v) for k,v in obj.items() if (v or v == False)}

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/abc.pyc in __instancecheck__(cls, instance)
    130         # Inline the cache checking when it's simple.
    131         subclass = getattr(instance, '__class__', None)
--> 132         if subclass is not None and subclass in cls._abc_cache:
    133             return True
    134         subtype = type(instance)

/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_weakrefset.pyc in __contains__(self, item)
     73         except TypeError:
     74             return False
---> 75         return wr in self.data
     76 
     77     def __reduce__(self):

RuntimeError: maximum recursion depth exceeded in cmp

Problem with MarathonClient get_group()

There seems to be a problem with MarathonClient.get_group() with Marathon 0.11.0. Code is expecting a 'group' key in group response, which doesn't exist:

$ python -c"from marathon import MarathonClient; c = MarathonClient('http://localhost:8000'); c.get_group('/example')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File ".../lib/python3.4/site-packages/marathon/client.py", line 275, in get_group
    return self._parse_response(response, MarathonGroup, resource_name='group')
  File ".../lib/python3.4/site-packages/marathon/client.py", line 53, in _parse_response
    target = response.json()[resource_name] if resource_name else response.json()
KeyError: 'group'

Zero values in apps/groups doesnt work

There is a json output broken.
You cannot use ports=[0], and port_index=0

Lets create simple app, put it in group, and see what we got.

from marathon.models import MarathonApp, MarathonHealthCheck,MarathonObject,MarathonGroup
import json

app = MarathonApp(
    id= '/somegroup/someapp',
    cmd='while true; do nc -l $PORT0;done ',
    cpus=1,
    mem=256,
    instances=1,
    ports=[ 0 ], # YOU ARE HERE!
    health_checks = [ MarathonHealthCheck(command=None,
                                          grace_period_seconds=300,
                                          interval_seconds=60,
                                          max_consecutive_failures=0,
                                          path=None,
                                          port_index=0, # ALSO AT PLACE
                                          protocol='TCP',
                                          timeout_seconds=20) ]
)

print json.dumps(
    json.loads(app.to_json()),
    sort_keys=True,
    indent=4, separators=(',', ': '))


group = MarathonGroup([app], dependencies=None, groups=None, id='/somegroup', version=None)


print json.dumps(
    json.loads(group.to_json()),
    sort_keys=True,
    indent=4, separators=(',', ': '))

Will output

{
    "cmd": "while true; do nc -l $PORT0;done ",
    "cpus": 1,
    "healthChecks": [
        {
            "gracePeriodSeconds": 300,
            "intervalSeconds": 60,
            "protocol": "TCP",
            "timeoutSeconds": 20 #WHERE IS MY PORTINDEX
        }
    ],
    "id": "/somegroup/someapp",
    "instances": 1,
    "mem": 256,
    "ports": [
        0
    ]
}
{
    "apps": [
        {
            "cmd": "while true; do nc -l $PORT0;done ",
            "cpus": 1,
            "healthChecks": [
                {
                    "gracePeriodSeconds": 300,
                    "intervalSeconds": 60,
                    "protocol": "TCP",
                    "timeoutSeconds": 20 #WHERE IS MY PortIndex
                }
            ],
            "id": "/somegroup/someapp",
            "instances": 1,
            "mem": 256,
            "ports": []  #WHERE IS MY PORTS! 
        }
    ],
    "id": "/somegroup"
}


YAML support for marathon-cli

I need a marathon-cli with YAML support. Should I add the functionality and open a pull request?

Previously, I opened a pull request of chronos-python, but looks like asher is away these days.

AttributeError on connection issues

It's expecting e.message to exist on any requests exception, but ConnectionError does not have that attribute:

    def _do_request(self, method, path, params=None, data=None):
        """Query Marathon server."""
        headers = {
            'Content-Type': 'application/json', 'Accept': 'application/json'}
        response = None
        servers = list(self.servers)
        while servers and response is None:
            server = servers.pop(0)
            url = ''.join([server.rstrip('/'), path])
            try:
                response = requests.request(
                    method, url, params=params, data=data, headers=headers,
                                            auth=self.auth, timeout=self.timeout)
                marathon.log.info('Got response from %s', server)
            except requests.exceptions.RequestException as e:
                marathon.log.error(
>                   'Error while calling %s: %s', url, e.message)
E               AttributeError: 'ConnectionError' object has no attribute 'message'

Task.app_id is None when using c.get_app("xxx").tasks

When I call with c.list_tasks(app_id), there's no problem.

But when I call with c.get_app(app_id).tasks[0].app_id, I found it's None.

After digging into the code a bit, I found it's caused by the marathon API /v2/apps/<app_id> and /v2/apps/<app_id>/tasks return different task format.

Hope it could be fixed.

portMapping isn't iterable

Hi,

I think you have a bug in MarathonContainerPortMapping class. Object generated from this class isn't iterable so bug is shown when you try to build docker container from this client. First MarathonContainerPortMapping doesn't returning iterator ,and second maybe it's better to this object (from class MarathonContainerPortMapping) contains list of PortMapping objects that contains container_port=, host_port=, service_port=, protocol'. And of course returning these elements through iterator.

from marathon import MarathonClient
from marathon.models.container import MarathonContainer
from marathon.models.container import MarathonDockerContainer
from marathon.models.container import MarathonContainerPortMapping
from marathon.models.container import MarathonContainerVolume
c = MarathonClient("localhost:8080")
portMapping = MarathonContainerPortMapping(container_port=80, host_port=0, service_port=1822, protocol='tcp')
docker = MarathonDockerContainer(image="ppoint/cmsv2", network="BRIDGE", port_mappings=portMapping)
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python2.7/site-packages/marathon/models/container.py", line 56, in init
for pm in (port_mappings or [])
TypeError: 'MarathonContainerPortMapping' object is not iterable

python --version
Python 2.7.5

Unexpected keyword argument: gpus

Error: init() got an unexpected keyword argument 'gpus'
TypeError in marathon.models.base in from_json

This error starting occurring after upgrading from DCOS 1.7 to 1.8, which comes packaged with Marathon 1.3.

AttributeError: module 'marathon' has no attribute 'MarathonClient'

Hi,

I installed package through pip

when i try to load *_"from marathon import MarathonClient" *_in script, i am getting an error message.
marathon.py file:

import marathon

c = marathon.MarathonClient("http://10.10.201.71:8080")

Error:
Traceback (most recent call last):
File "/Users/rajivreddy/PycharmProjects/Ezbuilder/source/marathon.py", line 1, in
import marathon
File "/Users/rajivreddy/PycharmProjects/Ezbuilder/source/marathon.py", line 3, in
c = marathon.MarathonClient("http://10.10.201.71:8080")
AttributeError: module 'marathon' has no attribute 'MarathonClient'

Python Version:
$python --version
Python 3.5.1 :: Continuum Analytics, Inc.

Marathon app name validation is broken

All app names seem to be considered invalid. The regular expression appears to be wrong

import re
ID_PATTERN = re.compile(r'^(?:(?:[a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*(?:[a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])$')
print ID_PATTERN.match('foo.bar') # None

MarathonHealthCheck class doesn't support 0.8.2

Marathon 0.8.2 added a new http health check option called ignoreHttp1xx. Calling list_apps or get_app for an http app raises the following exception.

  File "/usr/local/lib/python2.7/dist-packages/marathon/client.py", line 135, in list_apps
    apps = self._parse_response(response, MarathonApp, is_list=True, resource_name='apps')
  File "/usr/local/lib/python2.7/dist-packages/marathon/client.py", line 50, in _parse_response
    return [clazz.from_json(resource) for resource in target]
  File "/usr/local/lib/python2.7/dist-packages/marathon/models/base.py", line 30, in from_json
    return cls(**{to_snake_case(k): v for k,v in attributes.iteritems()})
  File "/usr/local/lib/python2.7/dist-packages/marathon/models/app.py", line 101, in __init__
    for hc in (health_checks or [])
  File "/usr/local/lib/python2.7/dist-packages/marathon/models/base.py", line 30, in from_json
    return cls(**{to_snake_case(k): v for k,v in attributes.iteritems()})
TypeError: __init__() got an unexpected keyword argument 'ignore_http1xx'

This fix to support an additional arg in MarathonHealthCheck is easy and I'll do a PR shortly.

The main question is, is there another way to get this right w/o having to add this argument?

Support for HA nodes

When Marathon is running in HA mode, there's multiple nodes that can provide the REST APIs. When one of them is down, we should automatically switch to other nodes.

So we should support a URI scheme like http://host1:8080,host2:8080/.

Does not understand readiness_check_results

It looks like #92 added readiness_checks but not readiness_check_results

  File "/tmp/local/lib/python2.7/site-packages/marathon/client.py", line 579, in list_deployments
    return self._parse_response(response, MarathonDeployment, is_list=True)
  File "/tmp/local/lib/python2.7/site-packages/marathon/client.py", line 48, in _parse_response
    return [clazz.from_json(resource) for resource in target]
  File "/tmp/local/lib/python2.7/site-packages/marathon/models/base.py", line 35, in from_json
    return cls(**{to_snake_case(k): v for k, v in attributes.items()})
  File "/tmp/local/lib/python2.7/site-packages/marathon/models/deployment.py", line 28, in __init__
    for a in (current_actions or [])
  File "/tmp/local/lib/python2.7/site-packages/marathon/models/base.py", line 35, in from_json
    return cls(**{to_snake_case(k): v for k, v in attributes.items()})
TypeError: __init__() got an unexpected keyword argument 'readiness_check_results'

Can we get another Pypi release?

The latest release doesn't have the persistent volume options and I need those for a project I'm working on. Looks like several other bug fixes are in master as well. I'd rather avoid installing directly from github if I can help it. Let me know if there's a better way to request these in the future.

MarathonClient is not compatible with marathon 0.14.1-1.0.455.ubuntu1404

client.list_apps()
{
"acceptedResourceRoles": null,
"backoffFactor": 1.15,
"backoffSeconds": 1,
"labels": {},
"cpus": 0.5,
"requirePorts": false,
"instances": 1,
"tasksStaged": 0,
"healthChecks": [],
"disk": 0,
"id": "/redis-3",
"maxLaunchDelaySeconds": 3600,
"container": {
"docker": {
"image": "redis:2.8.23",
"forcePullImage": false,
"network": "HOST",
"parameters": [
{
"value": "true",
"key": "oom-kill-disable"
}
],
"privileged": false
},
"type": "DOCKER",
"volumes": [
{
"hostPath": "/var/log/redis-2",
"containerPath": "/var/log/mesos",
"mode": "RW"
}
]
},
"deployments": [],
"version": "2015-12-10T07:21:46.529Z",
"env": {},
"uris": [],
"mem": 64,
"args": null,
"tasksRunning": 1,
"dependencies": [],
"user": null,
"storeUrls": [],
"tasksUnhealthy": 0,
"versionInfo": {
"lastScalingAt": "2015-12-10T07:21:46.529Z",
"lastConfigChangeAt": "2015-12-10T07:21:46.529Z"
},
"upgradeStrategy": {
"maximumOverCapacity": 1,
"minimumHealthCapacity": 1
},
"cmd": "redis-server --port $PORT0 --dbfilename redis-2",
"tasksHealthy": 0,
"executor": "",
"ipAddress": null,
"ports": [
10010
],
"constraints": []
}
Traceback (most recent call last):
File "", line 1, in
File "/home/zhechen/works/titan/marathon/client/client.py", line 132, in list_apps
apps = self._parse_response(response, MarathonApp, is_list=True, resource_name='apps')
File "/home/zhechen/works/titan/marathon/client/client.py", line 45, in _parse_response
return [clazz.from_json(resource) for resource in target]
File "/home/zhechen/works/titan/marathon/client/models/base.py", line 36, in from_json
return cls(**{to_snake_case(k): v for k,v in attributes.items()})
TypeError: init() got an unexpected keyword argument 'ip_address'

0.7.7 release

Hi there,
I saw that there's a new release tag (0.7.7) but that's not available for pip install yet. Are there any plans of making that available?

Thanks

create_app() not working for docker container

Hi, Below is the script i am trying to create a docker container using Marathon.
I doubt the issue with the json data sent in create_app()

from marathon import MarathonClient
from marathon.models import MarathonApp
from marathon.models.container import MarathonContainer
from marathon.models.container import MarathonDockerContainer

marathonApp = MarathonApp(cmd = 'while sleep 10; do date -u +%T; done', container = {'docker': {'image': 'ubuntu:latest', 'network': 'BRIDGE'}, 'type': 'DOCKER'}, cpus = 0.5, instances = 2, mem = 128)
marathonClient = MarathonClient('localhost:8082/')
marathonClient.create_app('/anbu/tst-ubuntu-ssh', marathonApp)

Getting error response as

Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.2/site-packages/marathon/client.py", line 109, in create_app
return self._parse_response(response, MarathonApp)
File "/usr/lib/python3.2/site-packages/marathon/client.py", line 52, in _parse_response
return clazz.from_json(target)
File "/usr/lib/python3.2/site-packages/marathon/models/base.py", line 30, in from_json
return cls({to_snake_case(k): v for k,v in attributes.items()})
File "/usr/lib/python3.2/site-packages/marathon/models/app.py", line 88, in init
else MarathonContainer.from_json(container)
File "/usr/lib/python3.2/site-packages/marathon/models/base.py", line 30, in from_json
return cls(
{to_snake_case(k): v for k,v in attributes.items()})
File "/usr/lib/python3.2/site-packages/marathon/models/container.py", line 25, in init
else MarathonDockerContainer().from_json(docker)
File "/usr/lib/python3.2/site-packages/marathon/models/base.py", line 30, in from_json
return cls(**{to_snake_case(k): v for k,v in attributes.items()})
AttributeError: 'NoneType' object has no attribute 'items'

Tried to dump the JSON sent in create_app().
{"cmd": "while sleep 10; do date -u +%T; done", "container": ["docker", "type"], "cpus": 0.5, "id": "/anbu/tst-ubuntu-ssh", "instances": 2, "mem": 128}

Supporting creating applications with a json file (or json-formatted string, or json object)

The marathon.MarathonApp object can be built by passing all the parameters defining an application.

It would be nice if there was a way to create marathon.MarathonApp with json content directly.

A workaround, given a json object, would be to use marathon.MarathonApp(**myapp_json), but there would still be issues for method parameters using snake-case and json keys using camelCase:

E.g.,

TypeError: __init__() got an unexpected keyword argument 'upgradeStrategy'

HTTP 400 returned with message, "Invalid JSON"

I'm trying to update a currently deployed app but it returns 400 returned with message, "Invalid JSON"

code snippit

client = MarathonClient(marathon_endpoints)
app = client.get_app(app_id)
client.update_app(app_id, app, force=True)

and the traceback :

No handlers could be found for logger "marathon"
Traceback (most recent call last):
  File "/update_app.py", line 49, in <module>
    client.update_app(app_id, app, force=True)
  File "/usr/lib/python2.7/site-packages/marathon/client.py", line 252, in update_app
    app.version = None
  File "/usr/lib/python2.7/site-packages/marathon/client.py", line 85, in _do_request
    raise MarathonHttpError(response)
MarathonHttpError: MarathonHttpError: HTTP 400 returned with message, "Invalid JSON"

I'm using marathon v0.15.3 and python-marathon v0.8.4
any ideas what's going wrong ?

Python 3 test not running

I don't think the .travis.yml is setup correctly to run python3 tests. I am hitting some python3 errors.

py27 and py34 are declared as dependencies and travis is correctly but the tox.ini is hard coding py2.7

Here is the log from the last stable travis run:

source ~/virtualenv/python3.4/bin/activate
$ python --version
Python 3.4.2  <-------CORRECT
$ pip --version
pip 6.0.7 from /home/travis/virtualenv/python3.4.2/lib/python3.4/site-packages (python 3.4)
install
1.67s$ pip install tox
You are using pip version 6.0.7, however version 7.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting tox
  Downloading tox-2.2.1-py2.py3-none-any.whl
Collecting pluggy<0.4.0,>=0.3.0 (from tox)
  Downloading pluggy-0.3.1-py2.py3-none-any.whl
Collecting virtualenv>=1.11.2 (from tox)
  Downloading virtualenv-13.1.2-py2.py3-none-any.whl (1.7MB)
    100% |################################| 1.7MB 172kB/s 
Requirement already satisfied (use --upgrade to upgrade): py>=1.4.17 in /home/travis/virtualenv/python3.4.2/lib/python3.4/site-packages (from tox)
Installing collected packages: virtualenv, pluggy, tox
Successfully installed pluggy-0.3.1 tox-2.2.1 virtualenv-13.1.2
8.51s$ make test
tox
py create: /home/travis/build/thefactory/marathon-python/.tox/py
py installdeps: -rrequirements.txt, pytest, mock
py develop-inst: /home/travis/build/thefactory/marathon-python
py installed: funcsigs==0.4,-e git+https://github.com/thefactory/marathon-python.git@a246bb2473e5dd1aa7775aade59cdf96f80e1d45#egg=marathon-dev,mock==1.3.0,pbr==1.8.1,py==1.4.31,pytest==2.8.4,requests==2.8.1,requests-mock==0.7.0,six==1.10.0,wheel==0.24.0
py runtests: PYTHONHASHSEED='3066453394'
py runtests: commands[0] | py.test -s tests
============================= test session starts ==============================
platform linux2 -- Python 2.7.9, pytest-2.8.4, py-1.4.31, pluggy-0.3.1  <-------NOT CORRECT!!!!!
rootdir: /home/travis/build/thefactory/marathon-python, inifile: 
collected 3 items 
tests/test_api.py ...

This is the issue i think: https://github.com/thefactory/marathon-python/blob/master/tox.ini#L9

Unknown event_type app_terminated_event

i used the event stream api, when the marathon responsed it sounds lose a event type class :

File "/usr/lib/python3.4/site-packages/marathon/client.py", line 742, in event_stream yield ef.process(event_data) File "/usr/lib/python3.4/site-packages/marathon/models/events.py", line 156, in process raise MarathonError('Unknown event_type: {}, data: {}'.format(event_type, event)) marathon.exceptions.MarathonError: Unknown event_type: app_terminated_event, data: {'timestamp': '2016-10-08T11:43:40.843Z', 'appId': '/order-service-u103', 'eventType': 'app_terminated_event'}

forcePullImage not honored by marathon-python (marathon 0.8.2 RC2)

marathon-python not working anymore with marathon 0.8.2 RC2 version.

Apparently because of this:
mesosphere/marathon@2a65700

It would be nice to ignore or implement forcePullImage feature from new version.

Traceback:

    ...
    app = marathon.get_app(marathon_app)
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/client.py", line 151, in get_app
    return self._parse_response(response, MarathonApp, resource_name='app')
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/client.py", line 52, in _parse_response
    return clazz.from_json(target)
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/models/base.py", line 32, in from_json
    return cls(**{to_snake_case(k): v for k,v in attributes.iteritems()})
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/models/app.py", line 88, in __init__
    else MarathonContainer.from_json(container)
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/models/base.py", line 32, in from_json
    return cls(**{to_snake_case(k): v for k,v in attributes.iteritems()})
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/models/container.py", line 25, in __init__
    else MarathonDockerContainer().from_json(docker)
  File "/root/.venvs/ho-api/lib/python2.7/site-packages/marathon/models/base.py", line 32, in from_json
    return cls(**{to_snake_case(k): v for k,v in attributes.iteritems()})
TypeError: __init__() got an unexpected keyword argument 'force_pull_image'

MarathonReadinessCheck class is absent

MarathonApp class field readiness_checks is described as

:type readiness_checks: list[:class:`marathon.models.app.ReadinessCheck`] or list[dict]

but there is no class ReadinessCheck

New release for 8.2 compatibility

I thought that the latest version of marathon-python covered the latest 0.8.2, but it doesn't fully, and I need this commit:
de5c91a

I'm am going to expand our integration tests to catch this, but till then can you make a new release for better compatibility for 0.8.2 that I can pull down?

Can we include the license file in the source release tarball ?

Hi!

When trying to package marathon-python for Fedora (and CentOS for RDO due to a dependency in magnum, I noticed that the LICENSE file is not included in the released source tarballs.

This is sort of an issue from a packaging perspective since the license can change throughout the history of a project and bundling the license in the source tarball provides some level of guarantee that this particular release was a specific license.

Example showing the license not in the release:

┬─[dmsimard@hostname:~/Downloads]─[09:36:01 AM]
╰─>$ wget https://pypi.python.org/packages/a3/9a/066a2b4126295532c55d6ad13b47c2f74eae3b411f60bdabe68d430ccd9f/marathon-0.8.7.tar.gz
--2016-10-31 09:36:07--  https://pypi.python.org/packages/a3/9a/066a2b4126295532c55d6ad13b47c2f74eae3b411f60bdabe68d430ccd9f/marathon-0.8.7.tar.gz
Resolving pypi.python.org (pypi.python.org)... 2a04:4e42:8::223, 151.101.32.223
Connecting to pypi.python.org (pypi.python.org)|2a04:4e42:8::223|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 20273 (20K) [binary/octet-stream]
Saving to: ‘marathon-0.8.7.tar.gz’

marathon-0.8.7.tar.gz                                                           100%[=====================================================================================================================================================================================================>]  19.80K  --.-KB/s    in 0.02s   

2016-10-31 09:36:07 (864 KB/s) - ‘marathon-0.8.7.tar.gz’ saved [20273/20273]

┬─[dmsimard@hostname:~/Downloads]─[09:36:07 AM]
╰─>$ tar -xzvf marathon-0.8.7.tar.gz 
marathon-0.8.7/
marathon-0.8.7/marathon/
marathon-0.8.7/marathon/util.py
marathon-0.8.7/marathon/_compat.py
marathon-0.8.7/marathon/models/
marathon-0.8.7/marathon/models/deployment.py
marathon-0.8.7/marathon/models/group.py
marathon-0.8.7/marathon/models/events.py
marathon-0.8.7/marathon/models/constraint.py
marathon-0.8.7/marathon/models/info.py
marathon-0.8.7/marathon/models/__init__.py
marathon-0.8.7/marathon/models/container.py
marathon-0.8.7/marathon/models/base.py
marathon-0.8.7/marathon/models/endpoint.py
marathon-0.8.7/marathon/models/app.py
marathon-0.8.7/marathon/models/queue.py
marathon-0.8.7/marathon/models/task.py
marathon-0.8.7/marathon/__init__.py
marathon-0.8.7/marathon/client.py
marathon-0.8.7/marathon/exceptions.py
marathon-0.8.7/PKG-INFO
marathon-0.8.7/setup.cfg
marathon-0.8.7/setup.py
marathon-0.8.7/marathon.egg-info/
marathon-0.8.7/marathon.egg-info/pbr.json
marathon-0.8.7/marathon.egg-info/requires.txt
marathon-0.8.7/marathon.egg-info/dependency_links.txt
marathon-0.8.7/marathon.egg-info/top_level.txt
marathon-0.8.7/marathon.egg-info/PKG-INFO
marathon-0.8.7/marathon.egg-info/SOURCES.txt
┬─[dmsimard@hostname:~/Downloads]─[09:36:11 AM]
╰─>$ tree marathon-0.8.7
marathon-0.8.7
├── marathon
│   ├── client.py
│   ├── _compat.py
│   ├── exceptions.py
│   ├── __init__.py
│   ├── models
│   │   ├── app.py
│   │   ├── base.py
│   │   ├── constraint.py
│   │   ├── container.py
│   │   ├── deployment.py
│   │   ├── endpoint.py
│   │   ├── events.py
│   │   ├── group.py
│   │   ├── info.py
│   │   ├── __init__.py
│   │   ├── queue.py
│   │   └── task.py
│   └── util.py
├── marathon.egg-info
│   ├── dependency_links.txt
│   ├── pbr.json
│   ├── PKG-INFO
│   ├── requires.txt
│   ├── SOURCES.txt
│   └── top_level.txt
├── PKG-INFO
├── setup.cfg
└── setup.py

3 directories, 26 files

ValueError when 401 Unauthorized is received

In this case, the return value is not JSON:

>>> c = MarathonClient('http://xulijian-mesos-online001-cqdx:8080', 'root', '111')
>>> c.list_apps()
ERROR:marathon:Got HTTP 401: <html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 401 Unauthorized</title>
</head>
<body>
<h2>HTTP ERROR: 401</h2>
<p>Problem accessing /v2/apps. Reason:
<pre>    Unauthorized</pre></p>
<hr /><i><small>Powered by Jetty://</small></i>




















</body>
</html>

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/home/iven/.local/share/virtualenvs/guardro/lib/python2.7/site-packages/marathon/client.py", line 134, in list_apps
    response = self._do_request('GET', '/v2/apps', params=params)
  File "/home/iven/.local/share/virtualenvs/guardro/lib/python2.7/site-packages/marathon/client.py", line 80, in _do_request
    raise MarathonHttpError(response)
  File "/home/iven/.local/share/virtualenvs/guardro/lib/python2.7/site-packages/marathon/exceptions.py", line 11, in __init__
    content = response.json()
  File "/home/iven/.local/share/virtualenvs/guardro/lib/python2.7/site-packages/requests/models.py", line 799, in json
    return json.loads(self.text, **kwargs)
  File "/usr/lib64/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/usr/lib64/python2.7/json/decoder.py", line 366, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib64/python2.7/json/decoder.py", line 384, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

Wrongly use version_info as task_stats

In line https://github.com/thefactory/marathon-python/blob/master/marathon/models/app.py#L149 version_info is used as task_stats if no task_stats provided. However the newest version marathon api show that their format are different and they are also different in what MarathonAppVersionInfo and MarathonTaskStats defined in code. So this will cause load a json from MarathonApp.to_json() result failed, as the task_stats format in json and what MarathonApp need is different.

Also taskStats will not returned from marathon if ?embed=apps.taskStats params is not specified and now the client has no way to specify this param, so the task_stats is always wrong.

I am not sure if there are some compatibility consideration to use version_info as task_stats. If not maybe this part should be removed, and also need a new param like embed_task_stats in get_app.

Update app with minimal=False doesn't work anymore

If we update app with minimal=False, marathon complains: {"message":"Invalid JSON","details":[{"path":"","errors":["You cannot specify both uris and fetch fields"]}]}

Don't know how to fix it.

>>> import logging
>>> logging.basicConfig()
>>> ...
>>> ...
>>> print app
MarathonApp::{'user': None, 'tasks': [], 'labels': {}, 'cpus': 2, 'instances': 0, 'readiness_checks': [], 'disk': 0, 'id': u'/minimal', 'tasks_running': 0, 'container': MarathonContainer::{"docker": {"forcePullImage": false, "image": "docker-registry.qiyi.virtual/guoguanglu/httpd:2.4", "network": "BRIDGE", "parameters": [], "portMappings": [{"containerPort": 8080, "hostPort": 0, "labels": {}, "name": null, "protocol": "tcp", "servicePort": 10000}], "privileged": false}, "type": "DOCKER", "volumes": []}, 'readiness_check_results': [], 'max_launch_delay_seconds': 600, 'tasks_staged': 0, 'deployments': [], 'version': None, 'env': {}, 'upgrade_strategy': MarathonUpgradeStrategy::{"maximumOverCapacity": 1, "minimumHealthCapacity": 1}, 'uris': [], 'port_definitions': [PortDefinition::{"labels": {}, "name": null, "port": 10000, "protocol": "tcp"}], 'version_info': MarathonAppVersionInfo::{"lastConfigChangeAt": "2016-08-10T07:25:07.472000Z", "lastScalingAt": "2016-08-10T07:30:06.899000Z"}, 'mem': 1024, 'task_kill_grace_period_seconds': None, 'args': None, 'backoff_factor': 1.15, 'task_stats': None, 'accepted_resource_roles': None, 'backoff_seconds': 1, 'health_checks': [MarathonHealthCheck::{"command": null, "gracePeriodSeconds": 300, "ignoreHttp1Xx": false, "intervalSeconds": 60, "maxConsecutiveFailures": 3, "path": "/", "port": 0, "portIndex": 0, "protocol": "HTTP", "timeoutSeconds": 20}, MarathonHealthCheck::{"command": null, "gracePeriodSeconds": 150, "ignoreHttp1Xx": false, "intervalSeconds": 60, "maxConsecutiveFailures": 3, "path": "/a", "port": 0, "portIndex": 0, "protocol": "HTTP", "timeoutSeconds": 20}], 'tasks_healthy': 0, 'tasks_unhealthy': 0, 'gpus': None, 'task_rate_limit': None, 'secrets': {}, 'fetch': [], 'cmd': u'sleep 100', 'require_ports': False, 'dependencies': [], 'residency': None, 'last_task_failure': MarathonTaskFailure::{"appId": "/minimal", "host": "mesos-master-dev043-cqdx.qiyi.virtual", "message": "Docker container run error: Container exited on error: exited with status 1", "slaveId": "e0a0bfc9-4663-45fa-8a12-4a7e3da9ca20-S1", "state": "TASK_FAILED", "taskId": "minimal.e7de7f4f-5ec7-11e6-b254-525400fd9fbc", "timestamp": "2016-08-10T06:59:02.932000Z", "version": "2016-08-10T06:57:05.525Z"}, 'executor': u'', 'store_urls': [], 'ports': [10000], 'constraints': []}
>>>
>>> marathon.update_app('minimal', app, minimal=True)
{u'deploymentId': u'a08b4f09-5b85-4354-b65f-c226a7e6df3d', u'version': u'2016-08-23T10:07:23.494Z'}
>>> 
>>> marathon.update_app('minimal', app, minimal=False)
ERROR:marathon:Got HTTP 400: {"message":"Invalid JSON","details":[{"path":"","errors":["You cannot specify both uris and fetch fields"]}]}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "marathon/client.py", line 260, in update_app
    'PUT', '/v2/apps/{app_id}'.format(app_id=app_id), params=params, data=data)
  File "marathon/client.py", line 88, in _do_request
    raise MarathonHttpError(response)
marathon.exceptions.MarathonHttpError: MarathonHttpError: HTTP 400 returned with message, "Invalid JSON"

update_app() no-ops if Version is passed

(Reported by @xtfxme: #13 (comment))

If app.version is set in the app passed to client.update_app(), Marathon won't apply the changes.

Workaround is to set the version to None:

app = c.get_app(ident)
app.instances = 10
app.version = None
c.update_app(app.id, app)

ignoreHttp1xx or ignoreHttp1Xx

Hi, first I would like to thank you for this lib, It is been useful for us here.

Said that, I would like to discuss about a possible bug I've found. But before sending my pull request I want to discuss the best solution to fix it.

I notice that the attribute "ignoreHttp1xx" of the "healthCheck" is been converted to "ignoreHttp1Xx" with the first "X" in capital letter. Based on the Marathon scheme referenced in this link https://mesosphere.github.io/marathon/docs/generated/api.html#v2_apps_post it is a boolean and the atribute "ignoreHttp1xx" with "x" in lower case.

However marathon accepts the atribute with capital "X". I'm using marathon version 0.13.0

After analysing the results, I look into the library code and realized that the problem is in the native python string method "w.tittle()" line https://github.com/thefactory/marathon-python/blob/master/marathon/util.py#L59

I've developed one possible solution rewriting the method title and it is like this :

thiago@ddcc947

As marathon also accepts the wrong attribute, this is generating a bug in fact. I'm creating an application that stores and validate the marathon atributes according with their scheme and it's generating a bug in my application because I'm having to create a validation too specific only for this atribute.

A simple way to see this behaviour is with this python code.

. 'httpxx'.title() and then 'http1xx'.title()

Thanks

Marathon Json encoder can't handle unicode strings

@kevinschoon I believe I have found a regression and traced back to #32. Probably efbaa67

We used to be able to have python-style Unicode strings like:

'image': u'localhost/fake_docker_url'

But this now produces this stacktrace:

     Traceback (most recent call last):
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/behave/model.py", line 1173, in run
          match.run(runner.context)
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/behave/model.py", line 1589, in run
          self.func(context, *args, **kwargs)
        File "/nail/home/kwa/Projects/paasta_tools/paasta_itests/steps/setup_marathon_job_steps.py", line 42, in create_complete_app
          fake_service_marathon_config,
        File "/nail/home/kwa/Projects/paasta_tools/paasta_tools/setup_marathon_job.py", line 300, in setup_service
          service_marathon_config.get_bounce_health_params(),
        File "/nail/home/kwa/Projects/paasta_tools/paasta_tools/setup_marathon_job.py", line 241, in deploy_service
          bounce_method, serviceinstance, cluster, instance_name, marathon_jobid, client)
        File "/nail/home/kwa/Projects/paasta_tools/paasta_tools/setup_marathon_job.py", line 138, in do_bounce
          bounce_lib.create_marathon_app(marathon_jobid, config, client)
        File "/nail/home/kwa/Projects/paasta_tools/paasta_tools/bounce_lib.py", line 159, in create_marathon_app
          client.create_app(app_id, MarathonApp(**config))
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/client.py", line 111, in create_app
          data = app.to_json()
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/models/base.py", line 41, in to_json
          return json.dumps(self.json_repr(minimal=True), cls=MarathonMinimalJsonEncoder, sort_keys=True)
        File "/usr/lib64/python2.7/json/__init__.py", line 250, in dumps
          sort_keys=sort_keys, **kw).encode(obj)
        File "/usr/lib64/python2.7/json/encoder.py", line 209, in encode
          chunks = list(chunks)
        File "/usr/lib64/python2.7/json/encoder.py", line 434, in _iterencode
          for chunk in _iterencode_dict(o, _current_indent_level):
        File "/usr/lib64/python2.7/json/encoder.py", line 408, in _iterencode_dict
          for chunk in chunks:
        File "/usr/lib64/python2.7/json/encoder.py", line 442, in _iterencode
          o = _default(o)
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 36, in default
          return self.default(obj.json_repr(minimal=True))
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 43, in default
          return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 43, in <dictcomp>
          return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 36, in default
          return self.default(obj.json_repr(minimal=True))
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 43, in default
          return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 43, in <dictcomp>
          return {k: self.default(v) for k,v in obj.items() if (v or v == False)}
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 45, in default
          return [self.default(e) for e in obj if (e or e == False)]
        File "/nail/home/kwa/Projects/paasta_tools/.tox/paasta_itests/local/lib/python2.7/site-packages/marathon/util.py", line 45, in default
          return [self.default(e) for e in obj if (e or e == False)]

If I remove the u, this no longer happens.

Do you know why this might be a problem?

cc @mrtyler @keshavdv

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.