Skip to content

Commit

Permalink
[marathon] Count running/pending deployments (#2788)
Browse files Browse the repository at this point in the history
* Count running or pending deployments

* Helper for loading JSON fixtures.

* Tests for marathon checks

* Normalize tabs/spaces and tidy whitespace.

* Clean up some linty stuff.

* Users should explicitly enable deployment-related metrics.

* Update example docs for Marathon.
  • Loading branch information
bradhe authored and masci committed Oct 25, 2016
1 parent 10de835 commit 4a1fca0
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 0 deletions.
6 changes: 6 additions & 0 deletions checks.d/marathon.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def check(self, instance):
auth = (user,password)
else:
auth = None

instance_tags = instance.get('tags', [])
default_timeout = self.init_config.get('default_timeout', self.DEFAULT_TIMEOUT)
timeout = float(instance.get('timeout', default_timeout))
Expand All @@ -56,6 +57,11 @@ def check(self, instance):
if attr in app:
self.gauge('marathon.' + attr, app[attr], tags=tags)

if instance.get('enable_deployment_metrics', False):
response = self.get_json(urljoin(url, "v2/deployments"), timeout, auth)
if response is not None:
self.gauge('marathon.deployments', len(response), tags=instance_tags)

def get_json(self, url, timeout, auth):
try:
r = requests.get(url, timeout=timeout, auth=auth)
Expand Down
1 change: 1 addition & 0 deletions conf.d/marathon.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ instances:
# if marathon is protected by basic auth
# user: "username"
# password: "password"
# enable_deployment_metrics: #Optional. Set to true if you want to receive send deployment related metrics. Adds an extra API call to Marathon.
4 changes: 4 additions & 0 deletions tests/checks/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import time
import traceback
import unittest
import json

# project
from checks import AgentCheck
Expand Down Expand Up @@ -132,6 +133,9 @@ def read_file(file_name, string_escape=True):
contents = contents.decode('string-escape')
return contents.decode("utf-8")

@staticmethod
def read_json_file(file_name, string_escape=True):
return json.loads(Fixtures.read_file(file_name, string_escape=string_escape))

class AgentCheckTest(unittest.TestCase):
DEFAULT_AGENT_CONFIG = {
Expand Down
88 changes: 88 additions & 0 deletions tests/checks/fixtures/marathon/apps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"apps": [
{
"id": "/my-app",
"cmd": "/usr/bin/run-app",
"args": null,
"user": null,
"env": {
"ENVIRONMENT": "development"
},
"instances": 10,
"cpus": 1.0,
"mem": 512,
"disk": 0,
"executor": "",
"constraints": [],
"ports": [
10002
],
"portDefinitions": [
{
"port": 10002,
"protocol": "tcp",
"labels": {}
}
],
"requirePorts": false,
"backoffSeconds": 1,
"backoffFactor": 1.15,
"maxLaunchDelaySeconds": 3600,
"container": {
"type": "DOCKER",
"volumes": [],
"docker": {
"image": "my-org/my-image:latest",
"network": "BRIDGE",
"portMappings": [
{
"containerPort": 80,
"hostPort": 0,
"servicePort": 10002,
"protocol": "tcp",
"labels": {}
}
],
"privileged": false,
"parameters": [],
"forcePullImage": true
}
},
"healthChecks": [
{
"path": "/health",
"protocol": "HTTP",
"portIndex": 0,
"gracePeriodSeconds": 5,
"intervalSeconds": 10,
"timeoutSeconds": 20,
"maxConsecutiveFailures": 3,
"ignoreHttp1xx": false
}
],
"readinessChecks": [],
"dependencies": [],
"upgradeStrategy": {
"minimumHealthCapacity": 0.5,
"maximumOverCapacity": 0.5
},
"labels": {
"HAPROXY_0_VHOST": "my-app.my-org.net",
"HAPROXY_GROUP": "my-app"
},
"acceptedResourceRoles": null,
"ipAddress": null,
"version": "2016-08-25T18:13:34.079Z",
"residency": null,
"versionInfo": {
"lastScalingAt": "2016-08-25T18:13:34.079Z",
"lastConfigChangeAt": "2016-08-25T16:58:48.416Z"
},
"tasksStaged": 5,
"tasksRunning": 10,
"tasksHealthy": 5,
"tasksUnhealthy": 0,
"deployments": []
}
]
}
87 changes: 87 additions & 0 deletions tests/checks/fixtures/marathon/deployments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
[
{
"affectedApps": [
"/test/service/srv1",
"/test/db/mongo1",
"/test/frontend/app1"
],
"currentStep": 2,
"currentActions": [
{
"action": "RestartApplication",
"app": "/test/frontend/app1",
"readinessChecks": [
{
"lastResponse": {
"body": "{}",
"contentType": "application/json",
"status": 500
},
"name": "myReadyCheck",
"ready": false,
"taskId": "test_frontend_app1.c9de6033"
}
]
}
],
"totalSteps": 9,
"id": "2e72dbf1-2b2a-4204-b628-e8bd160945dd",
"steps": [
[
{
"action": "RestartApplication",
"app": "/test/service/srv1"
}
],
[
{
"action": "RestartApplication",
"app": "/test/db/mongo1"
}
],
[
{
"action": "RestartApplication",
"app": "/test/frontend/app1"
}
],
[
{
"action": "KillAllOldTasksOf",
"app": "/test/frontend/app1"
}
],
[
{
"action": "KillAllOldTasksOf",
"app": "/test/db/mongo1"
}
],
[
{
"action": "KillAllOldTasksOf",
"app": "/test/service/srv1"
}
],
[
{
"action": "ScaleApplication",
"app": "/test/service/srv1"
}
],
[
{
"action": "ScaleApplication",
"app": "/test/db/mongo1"
}
],
[
{
"action": "ScaleApplication",
"app": "/test/frontend/app1"
}
]
],
"version": "2014-07-09T11:14:11.477Z"
}
]
68 changes: 68 additions & 0 deletions tests/checks/mock/test_marathon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# project
from tests.checks.common import AgentCheckTest
from tests.checks.common import Fixtures

DEPLOYMENT_METRICS_CONFIG = {
'init_config': {
'default_timeout': 5
},
'instances': [
{
'url': 'http://localhost:8080',
'enable_deployment_metrics': True
}
]
}

DEFAULT_CONFIG = {
'init_config': {
'default_timeout': 5
},
'instances': [
{
'url': 'http://localhost:8080'
}
]
}

def getMetricNames(metrics):
return [metric[0] for metric in metrics]

class MarathonCheckTest(AgentCheckTest):
CHECK_NAME = 'marathon'

def test_default_configuration(self):
def side_effect(url, timeout, auth):
if "v2/apps" in url:
return Fixtures.read_json_file("apps.json")
else:
raise Exception("unknown url:" + url)

self.run_check(DEFAULT_CONFIG, mocks={"get_json": side_effect})
self.assertMetric('marathon.apps', value=1)

# deployment-related metrics aren't included by default.
self.assertTrue('marathon.deployments' not in getMetricNames(self.metrics))

def test_empty_responses(self):
def side_effect(url, timeout, auth):
if "v2/apps" in url:
return {"apps": []}
else:
raise Exception("unknown url:" + url)

self.run_check(DEFAULT_CONFIG, mocks={"get_json": side_effect})
self.assertMetric('marathon.apps', value=0)

def test_enabled_deployment_metrics(self):
def side_effect(url, timeout, auth):
if "v2/apps" in url:
return Fixtures.read_json_file("apps.json")
elif "v2/deployments" in url:
return Fixtures.read_json_file("deployments.json")
else:
raise Exception("unknown url:" + url)

self.run_check(DEPLOYMENT_METRICS_CONFIG, mocks={"get_json": side_effect})
self.assertMetric('marathon.apps', value=1)
self.assertMetric('marathon.deployments', value=1)

0 comments on commit 4a1fca0

Please sign in to comment.