Remove Marathon AppDefinition checks

This commit also updates the tests to pass against the latest universe
This commit is contained in:
Tamar Ben-Shachar
2015-09-24 11:12:21 +02:00
committed by José Armando García Sancio
parent b373072811
commit ac12d45a8b
34 changed files with 458 additions and 845 deletions

View File

@@ -1,311 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"additionalProperties": false,
"definitions": {
"app": {
"additionalProperties": false,
"not": {
"allOf": [
{
"required": [
"cmd"
]
},
{
"required": [
"args"
]
}
]
},
"properties": {
"acceptedResourceRoles": {
"items": {
"type": "string"
},
"type": "array"
},
"args": {
"items": {
"type": "string"
},
"type": "array"
},
"backoffFactor": {
"minimum": 1.0,
"type": "number"
},
"backoffSeconds": {
"minimum": 0,
"type": "integer"
},
"cmd": {
"minLength": 1,
"type": "string"
},
"constraints": {},
"container": {
"additionalProperties": false,
"properties": {
"docker": {
"additionalProperties": false,
"properties": {
"forcePullImage": {
"type": "boolean"
},
"image": {
"type": "string"
},
"network": {
"type": "string"
},
"parameters": {
"items": {
"additionalProperties": false,
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"key",
"value"
],
"type": "object"
},
"type": "array"
},
"portMappings": {
"items": {
"additionalProperties": false,
"properties": {
"containerPort": {
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"hostPort": {
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"protocol": {
"type": "string"
},
"servicePort": {
"maximum": 65535,
"minimum": 0,
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"privileged": {
"type": "boolean"
}
},
"required": [
"image"
],
"type": "object"
},
"type": {
"type": "string"
},
"volumes": {
"items": {
"additionalProperties": false,
"properties": {
"containerPath": {
"type": "string"
},
"hostPath": {
"type": "string"
},
"mode": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"cpus": {
"minimum": 0,
"type": "number"
},
"dependencies": {
"items": {
"pattern": "^(\\/?((\\.{2})|([a-z0-9\\-]*))($|\\/))*$",
"type": "string"
},
"type": "array"
},
"disk": {
"minimum": 0,
"type": "number"
},
"env": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
},
"executor": {
"pattern": "^(|\\/\\/cmd|\\/?[^\\/]+(\\/[^\\/]+)*)$",
"type": "string"
},
"healthChecks": {
"items": {
"additionalProperties": false,
"properties": {
"command": {
"type": "string"
},
"gracePeriodSeconds": {
"minimum": 0,
"type": "integer"
},
"ignoreHttp1xx": {
"type": "boolean"
},
"intervalSeconds": {
"minimum": 0,
"type": "integer"
},
"maxConsecutiveFailures": {
"minimum": 0,
"type": "integer"
},
"path": {
"type": "string"
},
"portIndex": {
"minimum": 0,
"type": "integer"
},
"protocol": {
"type": "string"
},
"timeoutSeconds": {
"minimum": 0,
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"id": {
"pattern": "^(\\/?((\\.{2})|([a-z0-9][a-z0-9\\-]*[a-z0-9]+)|([a-z0-9]*))($|\\/))*$",
"type": "string"
},
"instances": {
"minimum": 0,
"type": "integer"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"maxLaunchDelaySeconds": {
"minimum": 0,
"type": "integer"
},
"mem": {
"minimum": 0,
"type": "number"
},
"ports": {
"items": {
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"type": "array"
},
"requirePorts": {
"type": "boolean"
},
"storeUrls": {
"items": {
"type": "string"
},
"type": "array"
},
"upgradeStrategy": {
"additionalProperties": false,
"properties": {
"maximumOverCapacity": {
"maximum": 1.0,
"minimum": 0.0,
"type": "number"
},
"minimumHealthCapacity": {
"maximum": 1.0,
"minimum": 0.0,
"type": "number"
}
},
"type": "object"
},
"uris": {
"items": {
"type": "string"
},
"type": "array"
},
"user": {
"type": "string"
},
"version": {
"format": "date-time",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
}
},
"properties": {
"apps": {
"items": {
"$ref": "#/definitions/app"
},
"type": "array"
},
"dependencies": {
"$ref": "#/definitions/app/properties/dependencies"
},
"groups": {
"items": {
"$ref": "#"
},
"type": "array"
},
"id": {
"$ref": "#/definitions/app/properties/id"
},
"version": {
"$ref": "#/definitions/app/properties/version"
},
"required": [
"id"
]
},
"type": "object"
}

View File

@@ -357,14 +357,6 @@ def _add(app_resource):
# Add application to marathon
client = marathon.create_client()
schema = client.get_app_schema()
if schema is None:
schema = _app_schema()
errs = util.validate_json(application_resource, schema)
if errs:
raise DCOSException(util.list_to_err(errs))
# Check that the application doesn't exist
app_id = client.normalize_app_id(application_resource['id'])
@@ -427,11 +419,6 @@ def _group_add(group_resource):
"""
group_resource = _get_resource(group_resource)
schema = _data_schema()
errs = util.validate_json(group_resource, schema)
if errs:
raise DCOSException(util.list_to_err(errs))
client = marathon.create_client()
@@ -536,13 +523,10 @@ def _group_update(group_id, properties, force):
client = marathon.create_client()
# Ensure that the group exists
current_group = client.get_group(group_id)
client.get_group(group_id)
schema = _data_schema()
group_resource = _parse_properties(properties, schema)
_validate_update(current_group, group_resource, schema)
deployment = client.update_group(group_id, group_resource, force)
properties = _parse_properties(properties)
deployment = client.update_group(group_id, properties, force)
emitter.publish('Created deployment {}'.format(deployment))
return 0
@@ -641,13 +625,10 @@ def _update(app_id, properties, force):
client = marathon.create_client()
# Ensure that the application exists
current_app = client.get_app(app_id)
client.get_app(app_id)
schema = _app_schema()
app_resource = _parse_properties(properties, schema)
_validate_update(current_app, app_resource, schema)
deployment = client.update_app(app_id, app_resource, force)
properties = _parse_properties(properties)
deployment = client.update_app(app_id, properties, force)
emitter.publish('Created deployment {}'.format(deployment))
return 0
@@ -672,68 +653,10 @@ def _group_scale(group_id, scale_factor, force):
return 0
def _validate_update(current_resource, properties, schema):
"""
Validate resource ("app" or "group") update
:param current_resource: Marathon app definition
:type current_resource: dict
:param properties: resource JSON
:type properties: dict
:param schema: JSON schema used to verify properties
:type schema: dict
:rtype: None
"""
updated_resource = _clean_up_resource_definition(current_resource.copy())
updated_resource.update(properties)
errs = util.validate_json(updated_resource, schema)
if errs:
raise DCOSException(util.list_to_err(errs))
def _clean_up_resource_definition(properties):
"""
Remove non user-specified properties and nulls from resource definition.
We remove fields that marathon adds because they aren't in the json-schema
because a user should not specity them. When we do a `get` to see the
current resource, we may see these fields, but leaving them will cause us
to unnecessarily fail the schema validation.
:param properties: resource JSON
:type properties: dict
:returns: resource JSON
:rtype: dict
"""
clean_properties = {}
ignore_properties = ["deployments", "lastTaskFailure", "tasks",
"tasksRunning", "tasksHealthy", "tasksUnhealthy",
"tasksStaged"]
for k, v in properties.items():
if v is not None and k not in ignore_properties:
# iterate through dict/list(s) to check all properties
if isinstance(v, list):
v = [value for value in v if value]
clean_properties[k] = [
_clean_up_resource_definition(elem)
if isinstance(elem, dict)
else elem
for elem in v
]
elif isinstance(v, dict):
clean_properties[k] = _clean_up_resource_definition(v)
else:
clean_properties[k] = v
return clean_properties
def _parse_properties(properties, schema):
def _parse_properties(properties):
"""
:param properties: JSON items in the form key=value
:type properties: [str]
:param schema: The JSON schema used to verify properties
:type schema: dict
:returns: resource JSON
:rtype: dict
"""
@@ -751,7 +674,7 @@ def _parse_properties(properties, schema):
resource_json = {}
for prop in properties:
key, value = jsonitem.parse_json_item(prop, schema)
key, value = jsonitem.parse_json_item(prop, None)
key = jsonitem.clean_value(key)
if key in resource_json:
@@ -759,7 +682,6 @@ def _parse_properties(properties, schema):
'Key {!r} was specified more than once'.format(key))
resource_json[key] = value
return resource_json
@@ -990,22 +912,3 @@ def _cli_config_schema():
pkg_resources.resource_string(
'dcoscli',
'data/config-schema/marathon.json').decode('utf-8'))
def _data_schema():
"""
:returns: schema for marathon data
:rtype: dict
"""
return json.loads(
pkg_resources.resource_string(
'dcoscli',
'data/marathon-group-schema.json').decode('utf-8'))
def _app_schema():
"""
:returns: schema for apps
:rtype: dict
"""
return _data_schema()['definitions']['app']

View File

@@ -306,7 +306,7 @@ def _log_marathon(follow, lines, ssh_config_file):
leader_ip = marathon.create_client().get_leader().split(':')[0]
cmd = ("ssh {0}core@{1} " +
"journalctl {2}-u marathon").format(
"journalctl {2}-u dcos-marathon").format(
ssh_options,
leader_ip,
journalctl_args)

View File

@@ -5,5 +5,5 @@ timeout = 5
ssl_verify = "false"
dcos_url = "http://dcos.snakeoil.mesosphere.com"
[package]
sources = [ "https://github.com/mesosphere/universe/archive/cli-tests.zip",]
sources = [ "https://github.com/mesosphere/universe/archive/cli-test-2.zip",]
cache = "tmp/cache"

View File

@@ -1,62 +0,0 @@
{
"apps": [
{
"backoffFactor": 1.15,
"backoffSeconds": 1,
"maxLaunchDelaySeconds": 3600,
"cmd": "python3 -m http.server 8080",
"constraints": [],
"container": {
"docker": {
"image": "python:3",
"network": "BRIDGE",
"portMappings": [
{
"containerPort": 8080,
"hostPort": 0,
"servicePort": 9000,
"protocol": "tcp"
},
{
"containerPort": 161,
"hostPort": 0,
"protocol": "udp"
}
]
},
"type": "DOCKER",
"volumes": []
},
"cpus": 0.5,
"dependencies": [],
"disk": 0.0,
"env": {},
"executor": "",
"healthChecks": [
{
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"maxConsecutiveFailures": 3,
"path": "/",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 20
}
],
"id": "/bridged-webapp",
"instances": 2,
"mem": 64.0,
"ports": [
10000,
10001
],
"requirePorts": false,
"storeUrls": [],
"upgradeStrategy": {
"minimumHealthCapacity": 1.0
},
"uris": [],
"version": "2014-09-25T02:26:59.256Z"
}
]
}

View File

@@ -1,70 +0,0 @@
{
"apps": [
{
"args": null,
"backoffFactor": 1.15,
"backoffSeconds": 1,
"maxLaunchDelaySeconds": 3600,
"cmd": "python3 -m http.server 8080",
"constraints": [],
"container": {
"docker": {
"image": "python:3",
"network": "BRIDGE",
"portMappings": [
{
"containerPort": 8080,
"hostPort": 0,
"servicePort": 9000,
"protocol": "tcp"
},
{
"containerPort": 161,
"hostPort": 0,
"protocol": "udp"
}
]
},
"type": "DOCKER",
"volumes": []
},
"cpus": 0.5,
"dependencies": [],
"deployments": [],
"disk": 0.0,
"env": {},
"executor": "",
"healthChecks": [
{
"command": null,
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"maxConsecutiveFailures": 3,
"path": "/",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 20
}
],
"id": "/bridged-webapp",
"instances": 2,
"mem": 64.0,
"ports": [
10000,
10001
],
"requirePorts": false,
"storeUrls": [],
"tasksRunning": 2,
"tasksHealthy": 2,
"tasksUnhealthy": 0,
"tasksStaged": 0,
"upgradeStrategy": {
"minimumHealthCapacity": 1.0
},
"uris": [],
"user": null,
"version": "2014-09-25T02:26:59.256Z"
}
]
}

View File

@@ -1,9 +1,9 @@
{
"id": "sleep-two-instances",
"id": "sleep-many-instances",
"cmd": "sleep 1000",
"cpus": 0.1,
"mem": 16,
"instances": 2,
"instances": 10,
"labels": {
"PACKAGE_ID": "test-app",
"PACKAGE_VERSION": "1.2.3"

View File

@@ -1,15 +0,0 @@
{
"groups": [
{
"id": "notgood",
"apps": [
{
"id": "hi",
"cmd": "sleep 0",
"badtype": 0
}
]
}
],
"id": "bad-group"
}

View File

@@ -1,15 +0,0 @@
{
"groups": [
{
"fakeapp": [
{
"cmds": "sleep 0",
"id": "hi",
"instances": 0
}
],
"id": "notgood"
}
],
"id": "bad-group"
}

View File

@@ -1,20 +1,80 @@
{
"pip": [
"dcos<1.0",
"git+https://github.com/mesosphere/dcos-helloworld.git#dcos-helloworld=0.1.0"
"dcos==0.1.13",
"git+https://github.com/mesosphere/dcos-cassandra.git#dcos-cassandra=0.1.0"
]
}
{
"id": "helloworld",
"cpus": 1.0,
"mem": 512,
"instances": 1,
"cmd": "python3 -m http.server {{port}}",
"container": {
"type": "DOCKER",
"docker": {
"image": "python:3",
"network": "HOST"
"id": "/cassandra/{{cassandra.cluster-name}}",
"cmd": "$(pwd)/jre*/bin/java $JAVA_OPTS -classpath cassandra-mesos-framework.jar io.mesosphere.mesos.frameworks.cassandra.framework.Main",
"instances": {{cassandra.framework.instances}},
"cpus": {{cassandra.framework.cpus}},
"mem": {{cassandra.framework.mem}},
"ports": [
0
],
"uris": [
"https://downloads.mesosphere.io/cassandra-mesos/artifacts/0.2.0-1/cassandra-mesos-0.2.0-1.tar.gz",
"https://downloads.mesosphere.io/java/jre-7u76-linux-x64.tar.gz"
],
"healthChecks": [
{
"gracePeriodSeconds": 120,
"intervalSeconds": 15,
"maxConsecutiveFailures": 0,
"path": "/health/cluster",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 5
},
{
"gracePeriodSeconds": 120,
"intervalSeconds": 30,
"maxConsecutiveFailures": 3,
"path": "/health/process",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 5
}
],
"labels": {
"DCOS_PACKAGE_FRAMEWORK_NAME": "cassandra.{{cassandra.cluster-name}}"
},
"env": {
"MESOS_ZK": "{{mesos.master}}"
,"JAVA_OPTS": "-Xms256m -Xmx256m"
,"CASSANDRA_CLUSTER_NAME": "{{cassandra.cluster-name}}"
,"CASSANDRA_NODE_COUNT": "{{cassandra.node-count}}"
,"CASSANDRA_SEED_COUNT": "{{cassandra.seed-count}}"
,"CASSANDRA_RESOURCE_CPU_CORES": "{{cassandra.resources.cpus}}"
,"CASSANDRA_RESOURCE_MEM_MB": "{{cassandra.resources.mem}}"
,"CASSANDRA_RESOURCE_DISK_MB": "{{cassandra.resources.disk}}"
,"CASSANDRA_FAILOVER_TIMEOUT_SECONDS": "{{cassandra.framework.failover-timeout-seconds}}"
,"CASSANDRA_DATA_DIRECTORY": "{{cassandra.data-directory}}"
,"CASSANDRA_HEALTH_CHECK_INTERVAL_SECONDS": "{{cassandra.health-check-interval-seconds}}"
,"CASSANDRA_ZK_TIMEOUT_MS": "{{cassandra.zk-timeout-ms}}"
,"CASSANDRA_BOOTSTRAP_GRACE_TIME_SECONDS": "{{cassandra.bootstrap-grace-time-seconds}}"
,"CASSANDRA_FRAMEWORK_MESOS_ROLE": "{{cassandra.framework.role}}"
,"CASSANDRA_DEFAULT_DC": "{{cassandra.dc.default-dc}}"
,"CASSANDRA_DEFAULT_RACK": "{{cassandra.dc.default-rack}}"
,"MESOS_AUTHENTICATE": "{{cassandra.framework.authentication.enabled}}"
{{#cassandra.dc.external-dcs}}
,"CASSANDRA_EXTERNAL_DC_{{name}}": "{{url}}"
{{/cassandra.dc.external-dcs}}
{{#cassandra.zk}} {{! if the full cassandra zk url has been specified use it }}
,"CASSANDRA_ZK": "{{cassandra.zk}}"
{{/cassandra.zk}}
{{^cassandra.zk}} {{! else, create a url based on convention and cluster name }}
,"CASSANDRA_ZK": "zk://master.mesos:2181/cassandra-mesos/{{cassandra.cluster-name}}"
{{/cassandra.zk}}
{{#cassandra.resource.heap-mb}}
,"CASSANDRA_RESOURCE_HEAP_MB": "{{cassandra.resource.heap-mb}}"
{{/cassandra.resource.heap-mb}}
{{#cassandra.framework.authentication.principal}}
,"DEFAULT_PRINCIPAL": "{{cassandra.framework.authentication.principal}}"
{{/cassandra.framework.authentication.principal}}
{{#cassandra.framework.authentication.secret}}
,"DEFAULT_SECRET": "{{cassandra.framework.authentication.secret}}"
{{/cassandra.framework.authentication.secret}}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{
"cmd": "LIBPROCESS_PORT=$PORT1 && ./bin/start --master zk://master.mesos:2181/mesos --checkpoint --failover_timeout 604800 --framework_name marathon-user --ha --zk zk://localhost:2181/mesos/marathon-user --http_port $PORT0 ",
"cmd": "LIBPROCESS_PORT=$PORT1 && ./bin/start --master zk://master.mesos:2181/mesos --checkpoint --decline_offer_duration 5000 --http_port $PORT0 --event_stream_max_outstanding_messages 50 --failover_timeout 604800 --framework_name marathon-user --ha --leader_proxy_connection_timeout 5000 --leader_proxy_read_timeout 10000 --local_port_max 20000 --local_port_min 10000 --marathon_store_timeout 2000 --max_tasks_per_offer 1 --max_tasks_per_offer_cycle 1000 --min_revive_offers_interval 5000 --revive_offers_for_new_apps --scale_apps_initial_delay 15000 --scale_apps_interval 300000 --zk_session_timeout 1800000 --zk zk://localhost:2181/mesos --mesos_leader_ui_url /mesos --zk_compression --zk_compression_threshold 65536 ",
"constraints": [
[
"hostname",
@@ -8,7 +8,7 @@
],
"container": {
"docker": {
"image": "mesosphere/marathon:v0.9.0",
"image": "mesosphere/marathon:v0.11.1",
"network": "HOST"
},
"type": "DOCKER"
@@ -17,17 +17,28 @@
"env": {
"JVM_OPTS": "-Xms256m -Xmx768m"
},
"healthChecks": [
{
"gracePeriodSeconds": 120,
"intervalSeconds": 10,
"maxConsecutiveFailures": 3,
"path": "/v2/info",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 5
}
],
"id": "marathon-user",
"instances": 1,
"labels": {
"DCOS_PACKAGE_FRAMEWORK_NAME": "marathon-user",
"DCOS_PACKAGE_IS_FRAMEWORK": "true",
"DCOS_PACKAGE_METADATA": "eyJkZXNjcmlwdGlvbiI6ICJBIGNsdXN0ZXItd2lkZSBpbml0IGFuZCBjb250cm9sIHN5c3RlbSBmb3Igc2VydmljZXMgaW4gY2dyb3VwcyBvciBEb2NrZXIgY29udGFpbmVycy4iLCAiZnJhbWV3b3JrIjogdHJ1ZSwgImltYWdlcyI6IHsiaWNvbi1sYXJnZSI6ICJodHRwczovL2Rvd25sb2Fkcy5tZXNvc3BoZXJlLmlvL21hcmF0aG9uL2Fzc2V0cy9pY29uLXNlcnZpY2UtbWFyYXRob24tbGFyZ2UucG5nIiwgImljb24tbWVkaXVtIjogImh0dHBzOi8vZG93bmxvYWRzLm1lc29zcGhlcmUuaW8vbWFyYXRob24vYXNzZXRzL2ljb24tc2VydmljZS1tYXJhdGhvbi1tZWRpdW0ucG5nIiwgImljb24tc21hbGwiOiAiaHR0cHM6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5pby9tYXJhdGhvbi9hc3NldHMvaWNvbi1zZXJ2aWNlLW1hcmF0aG9uLXNtYWxsLnBuZyJ9LCAibGljZW5zZXMiOiBbeyJuYW1lIjogIkFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wIiwgInVybCI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi9ibG9iL21hc3Rlci9MSUNFTlNFIn1dLCAibWFpbnRhaW5lciI6ICJzdXBwb3J0QG1lc29zcGhlcmUuaW8iLCAibmFtZSI6ICJtYXJhdGhvbiIsICJwb3N0SW5zdGFsbE5vdGVzIjogIk1hcmF0aG9uIERDT1MgU2VydmljZSBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgaW5zdGFsbGVkIVxuXG5cdERvY3VtZW50YXRpb246IGh0dHBzOi8vbWVzb3NwaGVyZS5naXRodWIuaW8vbWFyYXRob25cblx0SXNzdWVzOiBodHRwczovZ2l0aHViLmNvbS9tZXNvc3BoZXJlL21hcmF0aG9uL2lzc3Vlc1xuIiwgInBvc3RVbmluc3RhbGxOb3RlcyI6ICJUaGUgTWFyYXRob24gRENPUyBTZXJ2aWNlIGhhcyBiZWVuIHVuaW5zdGFsbGVkIGFuZCB3aWxsIG5vIGxvbmdlciBydW4uXG5QbGVhc2UgZm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgYXQgaHR0cDovL2RvY3MubWVzb3NwaGVyZS5jb20vc2VydmljZXMvbWFyYXRob24vI3VuaW5zdGFsbCB0byBjbGVhbiB1cCBhbnkgcGVyc2lzdGVkIHN0YXRlIiwgInByZUluc3RhbGxOb3RlcyI6ICJXZSByZWNvbW1lbmQgYSBtaW5pbXVtIG9mIG9uZSBub2RlIHdpdGggYXQgbGVhc3QgMiBDUFUncyBhbmQgMUdCIG9mIFJBTSBhdmFpbGFibGUgZm9yIHRoZSBNYXJhdGhvbiBTZXJ2aWNlLiIsICJzY20iOiAiaHR0cHM6Ly9naXRodWIuY29tL21lc29zcGhlcmUvbWFyYXRob24uZ2l0IiwgInRhZ3MiOiBbIm1lc29zcGhlcmUiLCAiZnJhbWV3b3JrIl0sICJ2ZXJzaW9uIjogIjAuOS4wIn0=",
"DCOS_PACKAGE_METADATA": "eyJkZXNjcmlwdGlvbiI6ICJBIGNsdXN0ZXItd2lkZSBpbml0IGFuZCBjb250cm9sIHN5c3RlbSBmb3Igc2VydmljZXMgaW4gY2dyb3VwcyBvciBEb2NrZXIgY29udGFpbmVycy4iLCAiZnJhbWV3b3JrIjogdHJ1ZSwgImltYWdlcyI6IHsiaWNvbi1sYXJnZSI6ICJodHRwczovL2Rvd25sb2Fkcy5tZXNvc3BoZXJlLmlvL21hcmF0aG9uL2Fzc2V0cy9pY29uLXNlcnZpY2UtbWFyYXRob24tbGFyZ2UucG5nIiwgImljb24tbWVkaXVtIjogImh0dHBzOi8vZG93bmxvYWRzLm1lc29zcGhlcmUuaW8vbWFyYXRob24vYXNzZXRzL2ljb24tc2VydmljZS1tYXJhdGhvbi1tZWRpdW0ucG5nIiwgImljb24tc21hbGwiOiAiaHR0cHM6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5pby9tYXJhdGhvbi9hc3NldHMvaWNvbi1zZXJ2aWNlLW1hcmF0aG9uLXNtYWxsLnBuZyJ9LCAibGljZW5zZXMiOiBbeyJuYW1lIjogIkFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wIiwgInVybCI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi9ibG9iL21hc3Rlci9MSUNFTlNFIn1dLCAibWFpbnRhaW5lciI6ICJzdXBwb3J0QG1lc29zcGhlcmUuaW8iLCAibmFtZSI6ICJtYXJhdGhvbiIsICJwb3N0SW5zdGFsbE5vdGVzIjogIk1hcmF0aG9uIERDT1MgU2VydmljZSBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgaW5zdGFsbGVkIVxuXG5cdERvY3VtZW50YXRpb246IGh0dHBzOi8vbWVzb3NwaGVyZS5naXRodWIuaW8vbWFyYXRob25cblx0SXNzdWVzOiBodHRwczovZ2l0aHViLmNvbS9tZXNvc3BoZXJlL21hcmF0aG9uL2lzc3Vlc1xuIiwgInBvc3RVbmluc3RhbGxOb3RlcyI6ICJUaGUgTWFyYXRob24gRENPUyBTZXJ2aWNlIGhhcyBiZWVuIHVuaW5zdGFsbGVkIGFuZCB3aWxsIG5vIGxvbmdlciBydW4uXG5QbGVhc2UgZm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgYXQgaHR0cDovL2RvY3MubWVzb3NwaGVyZS5jb20vc2VydmljZXMvbWFyYXRob24vI3VuaW5zdGFsbCB0byBjbGVhbiB1cCBhbnkgcGVyc2lzdGVkIHN0YXRlIiwgInByZUluc3RhbGxOb3RlcyI6ICJXZSByZWNvbW1lbmQgYSBtaW5pbXVtIG9mIG9uZSBub2RlIHdpdGggYXQgbGVhc3QgMiBDUFUncyBhbmQgMUdCIG9mIFJBTSBhdmFpbGFibGUgZm9yIHRoZSBNYXJhdGhvbiBTZXJ2aWNlLiIsICJzY20iOiAiaHR0cHM6Ly9naXRodWIuY29tL21lc29zcGhlcmUvbWFyYXRob24uZ2l0IiwgInRhZ3MiOiBbImluaXQiLCAibG9uZy1ydW5uaW5nIl0sICJ2ZXJzaW9uIjogIjAuMTEuMSJ9",
"DCOS_PACKAGE_NAME": "marathon",
"DCOS_PACKAGE_REGISTRY_VERSION": "1.0.0-rc1",
"DCOS_PACKAGE_RELEASE": "2",
"DCOS_PACKAGE_SOURCE": "https://github.com/mesosphere/universe/archive/cli-tests.zip",
"DCOS_PACKAGE_VERSION": "0.9.0"
"DCOS_PACKAGE_RELEASE": "6",
"DCOS_PACKAGE_SOURCE": "https://github.com/mesosphere/universe/archive/cli-test-2.zip",
"DCOS_PACKAGE_VERSION": "0.11.1"
},
"mem": 1024.0,
"ports": [

View File

@@ -0,0 +1,6 @@
{
"pip": [
"dcos==0.1.13",
"git+https://github.com/mesosphere/dcos-cassandra.git#dcos-cassandra=0.1.0"
]
}

View File

@@ -1,6 +0,0 @@
{
"pip": [
"dcos<1.0",
"git+https://github.com/mesosphere/dcos-helloworld.git#dcos-helloworld=0.1.0"
]
}

View File

@@ -19,8 +19,8 @@
"preInstallNotes": "We recommend a minimum of one node with at least 2 CPU's and 1GB of RAM available for the Marathon Service.",
"scm": "https://github.com/mesosphere/marathon.git",
"tags": [
"mesosphere",
"framework"
"init",
"long-running"
],
"version": "0.9.0"
"version": "0.11.1"
}

View File

@@ -1,5 +1,5 @@
{
"cmd": "LIBPROCESS_PORT=$PORT1 && ./bin/start --master zk://master.mesos:2181/mesos --checkpoint --failover_timeout 604800 --framework_name marathon-user --ha --zk zk://master.mesos:2181/universe/marathon-user --http_port $PORT0 ",
"cmd": "LIBPROCESS_PORT=$PORT1 && ./bin/start --master zk://master.mesos:2181/mesos --checkpoint --decline_offer_duration 5000 --http_port $PORT0 --event_stream_max_outstanding_messages 50 --failover_timeout 604800 --framework_name marathon-user --ha --leader_proxy_connection_timeout 5000 --leader_proxy_read_timeout 10000 --local_port_max 20000 --local_port_min 10000 --marathon_store_timeout 2000 --max_tasks_per_offer 1 --max_tasks_per_offer_cycle 1000 --min_revive_offers_interval 5000 --revive_offers_for_new_apps --scale_apps_initial_delay 15000 --scale_apps_interval 300000 --zk_session_timeout 1800000 --zk zk://master.mesos:2181/universe --mesos_leader_ui_url /mesos --zk_compression --zk_compression_threshold 65536 ",
"constraints": [
[
"hostname",
@@ -8,7 +8,7 @@
],
"container": {
"docker": {
"image": "mesosphere/marathon:v0.9.0",
"image": "mesosphere/marathon:v0.11.1",
"network": "HOST"
},
"type": "DOCKER"
@@ -17,17 +17,28 @@
"env": {
"JVM_OPTS": "-Xms256m -Xmx768m"
},
"healthChecks": [
{
"gracePeriodSeconds": 120,
"intervalSeconds": 10,
"maxConsecutiveFailures": 3,
"path": "/v2/info",
"portIndex": 0,
"protocol": "HTTP",
"timeoutSeconds": 5
}
],
"id": "marathon-user",
"instances": 1,
"labels": {
"DCOS_PACKAGE_FRAMEWORK_NAME": "marathon-user",
"DCOS_PACKAGE_IS_FRAMEWORK": "true",
"DCOS_PACKAGE_METADATA": "eyJkZXNjcmlwdGlvbiI6ICJBIGNsdXN0ZXItd2lkZSBpbml0IGFuZCBjb250cm9sIHN5c3RlbSBmb3Igc2VydmljZXMgaW4gY2dyb3VwcyBvciBEb2NrZXIgY29udGFpbmVycy4iLCAiZnJhbWV3b3JrIjogdHJ1ZSwgImltYWdlcyI6IHsiaWNvbi1sYXJnZSI6ICJodHRwczovL2Rvd25sb2Fkcy5tZXNvc3BoZXJlLmlvL21hcmF0aG9uL2Fzc2V0cy9pY29uLXNlcnZpY2UtbWFyYXRob24tbGFyZ2UucG5nIiwgImljb24tbWVkaXVtIjogImh0dHBzOi8vZG93bmxvYWRzLm1lc29zcGhlcmUuaW8vbWFyYXRob24vYXNzZXRzL2ljb24tc2VydmljZS1tYXJhdGhvbi1tZWRpdW0ucG5nIiwgImljb24tc21hbGwiOiAiaHR0cHM6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5pby9tYXJhdGhvbi9hc3NldHMvaWNvbi1zZXJ2aWNlLW1hcmF0aG9uLXNtYWxsLnBuZyJ9LCAibGljZW5zZXMiOiBbeyJuYW1lIjogIkFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wIiwgInVybCI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi9ibG9iL21hc3Rlci9MSUNFTlNFIn1dLCAibWFpbnRhaW5lciI6ICJzdXBwb3J0QG1lc29zcGhlcmUuaW8iLCAibmFtZSI6ICJtYXJhdGhvbiIsICJwb3N0SW5zdGFsbE5vdGVzIjogIk1hcmF0aG9uIERDT1MgU2VydmljZSBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgaW5zdGFsbGVkIVxuXG5cdERvY3VtZW50YXRpb246IGh0dHBzOi8vbWVzb3NwaGVyZS5naXRodWIuaW8vbWFyYXRob25cblx0SXNzdWVzOiBodHRwczovZ2l0aHViLmNvbS9tZXNvc3BoZXJlL21hcmF0aG9uL2lzc3Vlc1xuIiwgInBvc3RVbmluc3RhbGxOb3RlcyI6ICJUaGUgTWFyYXRob24gRENPUyBTZXJ2aWNlIGhhcyBiZWVuIHVuaW5zdGFsbGVkIGFuZCB3aWxsIG5vIGxvbmdlciBydW4uXG5QbGVhc2UgZm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgYXQgaHR0cDovL2RvY3MubWVzb3NwaGVyZS5jb20vc2VydmljZXMvbWFyYXRob24vI3VuaW5zdGFsbCB0byBjbGVhbiB1cCBhbnkgcGVyc2lzdGVkIHN0YXRlIiwgInByZUluc3RhbGxOb3RlcyI6ICJXZSByZWNvbW1lbmQgYSBtaW5pbXVtIG9mIG9uZSBub2RlIHdpdGggYXQgbGVhc3QgMiBDUFUncyBhbmQgMUdCIG9mIFJBTSBhdmFpbGFibGUgZm9yIHRoZSBNYXJhdGhvbiBTZXJ2aWNlLiIsICJzY20iOiAiaHR0cHM6Ly9naXRodWIuY29tL21lc29zcGhlcmUvbWFyYXRob24uZ2l0IiwgInRhZ3MiOiBbIm1lc29zcGhlcmUiLCAiZnJhbWV3b3JrIl0sICJ2ZXJzaW9uIjogIjAuOS4wIn0=",
"DCOS_PACKAGE_METADATA": "eyJkZXNjcmlwdGlvbiI6ICJBIGNsdXN0ZXItd2lkZSBpbml0IGFuZCBjb250cm9sIHN5c3RlbSBmb3Igc2VydmljZXMgaW4gY2dyb3VwcyBvciBEb2NrZXIgY29udGFpbmVycy4iLCAiZnJhbWV3b3JrIjogdHJ1ZSwgImltYWdlcyI6IHsiaWNvbi1sYXJnZSI6ICJodHRwczovL2Rvd25sb2Fkcy5tZXNvc3BoZXJlLmlvL21hcmF0aG9uL2Fzc2V0cy9pY29uLXNlcnZpY2UtbWFyYXRob24tbGFyZ2UucG5nIiwgImljb24tbWVkaXVtIjogImh0dHBzOi8vZG93bmxvYWRzLm1lc29zcGhlcmUuaW8vbWFyYXRob24vYXNzZXRzL2ljb24tc2VydmljZS1tYXJhdGhvbi1tZWRpdW0ucG5nIiwgImljb24tc21hbGwiOiAiaHR0cHM6Ly9kb3dubG9hZHMubWVzb3NwaGVyZS5pby9tYXJhdGhvbi9hc3NldHMvaWNvbi1zZXJ2aWNlLW1hcmF0aG9uLXNtYWxsLnBuZyJ9LCAibGljZW5zZXMiOiBbeyJuYW1lIjogIkFwYWNoZSBMaWNlbnNlIFZlcnNpb24gMi4wIiwgInVybCI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVzb3NwaGVyZS9tYXJhdGhvbi9ibG9iL21hc3Rlci9MSUNFTlNFIn1dLCAibWFpbnRhaW5lciI6ICJzdXBwb3J0QG1lc29zcGhlcmUuaW8iLCAibmFtZSI6ICJtYXJhdGhvbiIsICJwb3N0SW5zdGFsbE5vdGVzIjogIk1hcmF0aG9uIERDT1MgU2VydmljZSBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgaW5zdGFsbGVkIVxuXG5cdERvY3VtZW50YXRpb246IGh0dHBzOi8vbWVzb3NwaGVyZS5naXRodWIuaW8vbWFyYXRob25cblx0SXNzdWVzOiBodHRwczovZ2l0aHViLmNvbS9tZXNvc3BoZXJlL21hcmF0aG9uL2lzc3Vlc1xuIiwgInBvc3RVbmluc3RhbGxOb3RlcyI6ICJUaGUgTWFyYXRob24gRENPUyBTZXJ2aWNlIGhhcyBiZWVuIHVuaW5zdGFsbGVkIGFuZCB3aWxsIG5vIGxvbmdlciBydW4uXG5QbGVhc2UgZm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgYXQgaHR0cDovL2RvY3MubWVzb3NwaGVyZS5jb20vc2VydmljZXMvbWFyYXRob24vI3VuaW5zdGFsbCB0byBjbGVhbiB1cCBhbnkgcGVyc2lzdGVkIHN0YXRlIiwgInByZUluc3RhbGxOb3RlcyI6ICJXZSByZWNvbW1lbmQgYSBtaW5pbXVtIG9mIG9uZSBub2RlIHdpdGggYXQgbGVhc3QgMiBDUFUncyBhbmQgMUdCIG9mIFJBTSBhdmFpbGFibGUgZm9yIHRoZSBNYXJhdGhvbiBTZXJ2aWNlLiIsICJzY20iOiAiaHR0cHM6Ly9naXRodWIuY29tL21lc29zcGhlcmUvbWFyYXRob24uZ2l0IiwgInRhZ3MiOiBbImluaXQiLCAibG9uZy1ydW5uaW5nIl0sICJ2ZXJzaW9uIjogIjAuMTEuMSJ9",
"DCOS_PACKAGE_NAME": "marathon",
"DCOS_PACKAGE_REGISTRY_VERSION": "1.0.0-rc1",
"DCOS_PACKAGE_RELEASE": "2",
"DCOS_PACKAGE_SOURCE": "https://github.com/mesosphere/universe/archive/cli-tests.zip",
"DCOS_PACKAGE_VERSION": "0.9.0"
"DCOS_PACKAGE_RELEASE": "6",
"DCOS_PACKAGE_SOURCE": "https://github.com/mesosphere/universe/archive/cli-test-2.zip",
"DCOS_PACKAGE_VERSION": "0.11.1"
},
"mem": 1024.0,
"ports": [

View File

@@ -4,6 +4,10 @@
"additionalProperties": false,
"description": "Marathon specific configuration properties",
"properties": {
"access-control-allow-origin": {
"description": "The origin(s) to allow in Marathon. Not set by default. Example values are \"'*'\", or \"http://localhost:8888,http://domain.com\"",
"type": "string"
},
"artifact-store": {
"description": "URL to the artifact store. Examples: \"hdfs://localhost:54310/path/to/store\", \"file:///var/log/store\". For details, see the artifact store docs: https://mesosphere.github.io/marathon/docs/artifact-store.html.",
"type": "string"
@@ -19,11 +23,47 @@
"minimum": 0.0,
"type": "number"
},
"decline-offer-duration": {
"default": 5000,
"description": "The duration (milliseconds) for which to decline offers by default",
"minimum": 100,
"type": "integer"
},
"default-accepted-resource-roles": {
"description": "Default for the defaultAcceptedResourceRoles attribute of all app definitions as a comma-separated list of strings. This defaults to all roles for which this Marathon instance is configured to receive offers.",
"type": "string"
},
"disable-http": {
"default": false,
"description": "Disable listening for HTTP requests completely. HTTPS is unaffected.",
"type": "boolean"
},
"disable_metrics": {
"default": false,
"description": "Disable metric measurement of service method calls",
"type": "boolean"
},
"enable-https": {
"default": false,
"description": "Enables the HTTPS protocol. Use in conjunction with ssl-keystore-path and ssl-keystore-password.",
"type": "boolean"
},
"enable-tracing": {
"default": false,
"description": "Enable trace logging of service method calls",
"type": "boolean"
},
"env-vars-prefix": {
"default": "",
"description": "Prefix to use for environment variables",
"type": "string"
},
"event-stream-max-outstanding-messages": {
"default": 50,
"description": "The event stream buffers events, that are not already consumed by clients. This number defines the number of events that get buffered on the server side, before messages are dropped.",
"minimum": 10,
"type": "integer"
},
"event-subscriber": {
"description": "Event subscriber module to enable. Currently the only valid value is \"http_callback\".",
"type": "string"
@@ -57,6 +97,11 @@
"description": "Pre-configured http callback URLs. Valid only in conjunction with --event_subscriber http_callback. Additional callback URLs may also be set dynamically via the REST API.",
"type": "string"
},
"http-max-concurrent-requests": {
"description": "The number of concurrent http requests, that are allowed before rejecting.",
"minimum": 1,
"type": "integer"
},
"http-port": {
"default": 0,
"description": "The port on which to listen for HTTP requests.",
@@ -87,23 +132,55 @@
"minimum": 0,
"type": "number"
},
"leader-proxy-connection-timeout": {
"default": 5000,
"description": "Maximum time, in milliseconds, to wait for connecting to the current Marathon leader from another Marathon instance.",
"minimum": 0,
"type": "integer"
},
"leader-proxy-read-timeout": {
"default": 10000,
"description": "Maximum time, in milliseconds, for reading from the current Marathon leader.",
"minimum": 0,
"type": "integer"
},
"local-port-max": {
"default": 20000,
"description": "Maximum port number to use when assigning service ports to apps.",
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"local-port-min": {
"default": 10000,
"description": "Minimum port number to use when assigning service ports to apps.",
"maximum": 65535,
"minimum": 0,
"type": "integer"
},
"marathon-store-timeout": {
"default": 2000,
"description": "Maximum time in milliseconds, to wait for persistent storage operations to complete.",
"minimum": 0,
"type": "integer"
},
"max-apps": {
"description": "The maximum number of applications that may be created.",
"minimum": 0,
"type": "integer"
},
"max-tasks-per-offer": {
"default": 1,
"description": "Maximally launch this number of tasks per offer.",
"minimum": 1,
"type": "integer"
},
"max-tasks-per-offer-cycle": {
"default": 1000,
"description": "Maximally launch this number of tasks per offer cycle.",
"minimum": 1,
"type": "integer"
},
"mem": {
"default": 1024.0,
"description": "Memory (MB) to allocate to each Marathon task.",
@@ -118,10 +195,21 @@
"description": "The path to the Mesos secret file containing the authentication secret.",
"type": "string"
},
"mesos-leader-ui-url": {
"default": "/mesos",
"description": "The host and port (e.g. http://mesos_host:5050) of the Mesos master.",
"type": "string"
},
"mesos-role": {
"description": "Mesos role for this framework.",
"type": "string"
},
"min-revive-offers-interval": {
"default": 5000,
"description": "Do not ask for all offers (also already seen ones) more often than this interval (ms).",
"minimum": 200,
"type": "integer"
},
"reconciliation-initial-delay": {
"description": "The delay, in milliseconds, before Marathon begins to periodically perform task reconciliation operations.",
"minimum": 0,
@@ -132,6 +220,23 @@
"minimum": 0,
"type": "integer"
},
"revive-offers-for-new-apps": {
"default": true,
"description": "Whether to call reviveOffers for new or changed apps.",
"type": "boolean"
},
"scale-apps-initial-delay": {
"default": 15000,
"description": "This is the length of time, in milliseconds, before Marathon begins to periodically attempt to scale apps",
"minimum": 0,
"type": "integer"
},
"scale-apps-interval": {
"default": 300000,
"description": "This is the length of time, in milliseconds, between task scale apps.",
"minimum": 1000,
"type": "integer"
},
"ssl-keystore-password": {
"description": "Password for the keystore supplied with the marathon/ssl-keystore-path option.",
"type": "string"
@@ -154,11 +259,28 @@
"description": "Parent ZooKeeper URL for storing state. The framework name is joined to the path of this value. Format: zk://host1:port1,host2:port2,.../path",
"type": "string"
},
"zk-compression": {
"default": true,
"description": "Enable compression of zk nodes, if the size of the node is bigger than the configured threshold.",
"type": "boolean"
},
"zk-compression-threshold": {
"default": 65536,
"description": "Threshold in bytes, when compression is applied to the zk node (Default: 64 KB).",
"minimum": 0,
"type": "integer"
},
"zk-max-versions": {
"description": "Limit the number of versions stored for one entity.",
"minimum": 0,
"type": "integer"
},
"zk-session-timeout": {
"default": 1800000,
"description": "The timeout for zookeeper sessions in milliseconds.",
"minimum": 0,
"type": "integer"
},
"zk-timeout": {
"description": "Timeout for ZooKeeper in milliseconds.",
"minimum": 0,
@@ -177,7 +299,7 @@
"type": "object"
},
"mesos": {
"depscription": "Mesos specific configuration properties",
"description": "Mesos specific configuration properties",
"properties": {
"master": {
"default": "zk://master.mesos:2181/mesos",

View File

@@ -1,3 +1,7 @@
0.11.1
0.11.0
0.10.1
0.9.2
0.9.0
0.9.0-RC3
0.8.1

View File

@@ -0,0 +1,8 @@
{
"marathon": {
"zk": "zk://master.mesos:2181/universe/marathon-user",
"framework-name": "marathon-user",
"mem": 850,
"cpus": 1
}
}

View File

@@ -1,7 +1,8 @@
{
"marathon": {
"zk": "zk://master.mesos:2181/universe2",
"mem": 512,
"zk": "zk://master.mesos:2181/universe/marathon-user2",
"framework-name": "marathon-user",
"mem": 850,
"cpus": 1
}
}

View File

@@ -1,6 +1,6 @@
[package]
cache = "tmp/cache"
sources = [ "https://github.com/mesosphere/universe/archive/cli-tests.zip",]
sources = [ "https://github.com/mesosphere/universe/archive/cli-test-2.zip",]
[core]
timeout = 5
dcos_url = "https://dcos.snakeoil.mesosphere.com"

View File

@@ -28,6 +28,7 @@ def slave_fixture():
"pid": "slave(1)@172.17.8.101:5051",
"registered_time": 1435625024.42234,
"reregistered_time": 1435625024.42234,
"reserved_resources": {},
"resources": {
"cpus": 4.0,
"disk": 10823,
@@ -35,6 +36,12 @@ def slave_fixture():
"ports": ("[1025-2180, 2182-3887, 3889-5049, 5052-8079, " +
"8082-8180, 8182-65535]")
},
"unreserved_resources": {
"cpus": 4,
"disk": 10823,
"mem": 2933,
"ports": "[1025-2180, 2182-3887, 3889-5049, 5052-32000]"
},
"used_resources": {
"cpus": 0.0,
"disk": 0,

View File

@@ -9,6 +9,7 @@ def framework_fixture():
return Framework({
"active": True,
"capabilities": [],
"checkpoint": True,
"completed_tasks": [],
"executors": [],

View File

@@ -196,11 +196,13 @@ def watch_all_deployments(count=300):
watch_deployment(dep['id'], count)
def wait_for_service(service_name, max_count=300):
def wait_for_service(service_name, number_of_services=1, max_count=300):
"""Wait for service to register with Mesos
:param service_name: name of service
:type service_name: str
:param number_of_services: number of services with that name
:type number_of_services: int
:param max_count: max number of seconds to wait
:type max_count: int
:rtype: None
@@ -210,24 +212,25 @@ def wait_for_service(service_name, max_count=300):
while count < max_count:
services = get_services()
for service in services:
if service['name'] == service_name:
return
if (len([service for service in services
if service['name'] == service_name]) >= number_of_services):
return
count += 1
def add_app(app_path, deploy=False):
def add_app(app_path, wait=True):
""" Add an app, and wait for it to deploy
:param app_path: path to app's json definition
:type app_path: str
:param wait: whether to wait for the deploy
:type wait: bool
:rtype: None
"""
assert_command(['dcos', 'marathon', 'app', 'add', app_path])
if deploy:
if wait:
watch_all_deployments()
@@ -447,7 +450,7 @@ def file_json(path):
@contextlib.contextmanager
def app(path, app_id, deploy=False):
def app(path, app_id, wait=True):
"""Context manager that deploys an app on entrance, and removes it on
exit.
@@ -455,14 +458,17 @@ def app(path, app_id, deploy=False):
:type path: str
:param app_id: app id
:type app_id: str
:param wait: whether to wait for the deploy
:type wait: bool
:rtype: None
"""
add_app(path, deploy)
add_app(path, wait)
try:
yield
finally:
remove_app(app_id)
watch_all_deployments()
@contextlib.contextmanager

View File

@@ -80,7 +80,7 @@ core.ssl_verify=false
core.timeout=5
package.cache=tmp/cache
package.sources=['https://github.com/mesosphere/universe/archive/\
cli-tests.zip']
cli-test-2.zip']
"""
assert_command(['dcos', 'config', 'show'],
stdout=stdout,
@@ -178,11 +178,11 @@ def test_append_empty_list(env):
config_set('package.sources', '[]', env)
_append_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
_get_value(
'package.sources',
['https://github.com/mesosphere/universe/archive/cli-tests.zip'],
['https://github.com/mesosphere/universe/archive/cli-test-2.zip'],
env)
@@ -190,11 +190,11 @@ def test_prepend_empty_list(env):
config_set('package.sources', '[]', env)
_prepend_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
_get_value(
'package.sources',
['https://github.com/mesosphere/universe/archive/cli-tests.zip'],
['https://github.com/mesosphere/universe/archive/cli-test-2.zip'],
env)
@@ -205,7 +205,7 @@ def test_append_list(env):
env)
_get_value(
'package.sources',
['https://github.com/mesosphere/universe/archive/cli-tests.zip',
['https://github.com/mesosphere/universe/archive/cli-test-2.zip',
'https://github.com/mesosphere/universe/archive/version-2.x.zip'],
env)
config_unset('package.sources', '1', env)
@@ -219,7 +219,7 @@ def test_prepend_list(env):
_get_value(
'package.sources',
['https://github.com/mesosphere/universe/archive/version-2.x.zip',
'https://github.com/mesosphere/universe/archive/cli-tests.zip'],
'https://github.com/mesosphere/universe/archive/cli-test-2.zip'],
env)
config_unset('package.sources', '0', env)
@@ -270,7 +270,7 @@ def test_unset_output(env):
def test_unset_index_output(env):
stdout = (
b"[package.sources]: removed element "
b"'https://github.com/mesosphere/universe/archive/cli-tests.zip' "
b"'https://github.com/mesosphere/universe/archive/cli-test-2.zip' "
b"at index '0'\n"
)
@@ -280,14 +280,14 @@ def test_unset_index_output(env):
_prepend_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
def test_set_whole_list(env):
config_set(
'package.sources',
'["https://github.com/mesosphere/universe/archive/cli-tests.zip"]',
'["https://github.com/mesosphere/universe/archive/cli-test-2.zip"]',
env)
@@ -314,7 +314,7 @@ def test_unset_list_index(env):
env)
_prepend_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
@@ -358,7 +358,7 @@ def test_validate(env):
def test_validation_error(env):
source = ["https://github.com/mesosphere/universe/archive/cli-tests.zip"]
source = ["https://github.com/mesosphere/universe/archive/cli-test-2.zip"]
config_unset('package.sources', None, env)
stdout = b"Error: missing required property 'sources'.\n"
@@ -422,12 +422,12 @@ def test_url_validation(env):
def test_append_url_validation(env):
default_value = ('["https://github.com/mesosphere/universe/archive/'
'cli-tests.zip"]')
'cli-test-2.zip"]')
config_set('package.sources', '[]', env)
_append_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
_append_value(
'package.sources',
@@ -450,12 +450,12 @@ def test_append_url_validation(env):
def test_prepend_url_validation(env):
default_value = ('["https://github.com/mesosphere/universe/archive/'
'cli-tests.zip"]')
'cli-test-2.zip"]')
config_set('package.sources', '[]', env)
_prepend_value(
'package.sources',
'https://github.com/mesosphere/universe/archive/cli-tests.zip',
'https://github.com/mesosphere/universe/archive/cli-test-2.zip',
env)
_prepend_value(
'package.sources',

View File

@@ -13,6 +13,8 @@ from .common import (app, assert_command, assert_lines, config_set,
config_unset, exec_command, list_deployments, popen_tty,
show_app, watch_all_deployments, watch_deployment)
_ZERO_INSTANCE_APP_INSTANCES = 100
def test_help():
stdout = b"""Deploy and manage applications on the DCOS
@@ -329,8 +331,7 @@ def test_stop_missing_app():
def test_stop_app():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'app', 'stop', 'zero-instance-app'])
@@ -356,29 +357,26 @@ def test_update_missing_app():
returncode=1)
def test_update_missing_field():
with _zero_instance_app():
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'app', 'update',
'zero-instance-app', 'missing="a string"'])
assert returncode == 1
assert stdout == b''
assert stderr.decode('utf-8').startswith(
"Error: 'missing' is not a valid property. "
"Possible properties are: ")
def test_update_bad_type():
with _zero_instance_app():
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'app', 'update',
'zero-instance-app', 'cpus="a string"'])
stderr_end = b"""{
"details": [
{
"errors": [
"error.expected.jsnumber"
],
"path": "/cpus"
}
],
"message": "Invalid JSON"
}
"""
assert returncode == 1
assert stderr.decode('utf-8').startswith(
"Unable to parse 'a string' as a float: could not convert string "
"to float: ")
assert stderr_end in stderr
assert stdout == b''
@@ -388,6 +386,8 @@ def test_update_invalid_request():
assert returncode == 1
assert stdout == b''
stderr = stderr.decode()
# TODO (tamar): this becomes 'Error: App '/{' does not exist\n"'
# in Marathon 0.11.0
assert stderr.startswith('Error on request')
assert stderr.endswith('HTTP 400: Bad Request\n')
@@ -456,9 +456,7 @@ def test_restarting_missing_app():
def test_restarting_app():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'app', 'restart', 'zero-instance-app'])
@@ -508,7 +506,7 @@ def test_list_empty_deployment():
def test_list_deployment():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', _ZERO_INSTANCE_APP_INSTANCES)
list_deployments(1)
@@ -519,7 +517,7 @@ def test_list_deployment_table():
"""
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', _ZERO_INSTANCE_APP_INSTANCES)
assert_lines(['dcos', 'marathon', 'deployment', 'list'], 2)
@@ -531,7 +529,7 @@ def test_list_deployment_missing_app():
def test_list_deployment_app():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', _ZERO_INSTANCE_APP_INSTANCES)
list_deployments(1, 'zero-instance-app')
@@ -544,7 +542,7 @@ def test_rollback_missing_deployment():
def test_rollback_deployment():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', _ZERO_INSTANCE_APP_INSTANCES)
result = list_deployments(1, 'zero-instance-app')
returncode, stdout, stderr = exec_command(
@@ -557,12 +555,13 @@ def test_rollback_deployment():
assert 'version' in result
assert stderr == b''
watch_all_deployments()
list_deployments(0)
def test_stop_deployment():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', _ZERO_INSTANCE_APP_INSTANCES)
result = list_deployments(1, 'zero-instance-app')
assert_command(
@@ -577,7 +576,7 @@ def test_watching_missing_deployment():
def test_watching_deployment():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
_start_app('zero-instance-app', 10)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
list_deployments(0, 'zero-instance-app')
@@ -595,8 +594,7 @@ def test_list_empty_task_not_running_app():
def test_list_tasks():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
_list_tasks(3)
@@ -610,16 +608,14 @@ def test_list_tasks_table():
def test_list_app_tasks():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
_list_tasks(3, 'zero-instance-app')
def test_list_missing_app_tasks():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
_list_tasks(0, 'missing-id')
@@ -638,8 +634,7 @@ def test_show_missing_task():
def test_show_task():
with _zero_instance_app():
_start_app('zero-instance-app', 3)
result = list_deployments(1, 'zero-instance-app')
watch_deployment(result[0]['id'], 60)
watch_all_deployments()
result = _list_tasks(3, 'zero-instance-app')
returncode, stdout, stderr = exec_command(
@@ -667,10 +662,11 @@ def test_bad_configuration():
def test_app_locked_error():
with app('tests/data/marathon/apps/sleep_two_instances.json',
'/sleep-two-instances'):
with app('tests/data/marathon/apps/sleep_many_instances.json',
'/sleep-many-instances',
wait=False):
assert_command(
['dcos', 'marathon', 'app', 'stop', 'sleep-two-instances'],
['dcos', 'marathon', 'app', 'stop', 'sleep-many-instances'],
returncode=1,
stderr=(b'App or group is locked by one or more deployments. '
b'Override with --force.\n'))

View File

@@ -42,42 +42,27 @@ def test_show_group():
_show_group('test-group')
def test_add_bad_app():
with open('tests/data/marathon/groups/bad_app.json') as fd:
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'group', 'add'],
stdin=fd)
expected = "Error: Additional properties are not allowed" + \
" ('badtype' was unexpected)"
assert returncode == 1
assert stdout == b''
assert stderr.decode('utf-8').startswith(expected)
def test_add_bad_group():
with open('tests/data/marathon/groups/bad_group.json') as fd:
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'group', 'add'],
stdin=fd)
expected = "Error: Additional properties are not allowed" + \
" ('fakeapp' was unexpected)"
assert returncode == 1
assert stdout == b''
assert stderr.decode('utf-8').startswith(expected)
def test_add_bad_complicated_group():
with open('tests/data/marathon/groups/complicated_bad.json') as fd:
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'group', 'add'],
stdin=fd)
err = "Error: missing required property 'id'"
err = b"""{
"details": [
{
"errors": [
"error.path.missing"
],
"path": "/groups(0)/apps(0)/id"
}
],
"message": "Invalid JSON"
}
"""
assert returncode == 1
assert stdout == b''
assert err in stderr.decode('utf-8')
assert err in stderr
def test_update_group():
@@ -110,19 +95,6 @@ def test_update_missing_group():
returncode=1)
def test_update_missing_field():
with _group(GOOD_GROUP, 'test-group'):
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'group', 'update',
'test-group/sleep', 'missing="a string"'])
assert returncode == 1
assert stdout == b''
assert stderr.decode('utf-8').startswith(
"Error: 'missing' is not a valid property. "
"Possible properties are: ")
def test_scale_group():
_deploy_group('tests/data/marathon/groups/scale.json')
returncode, stdout, stderr = exec_command(['dcos', 'marathon', 'group',

View File

@@ -51,7 +51,7 @@ def _chronos_description(app_ids):
"maintainer": "support@mesosphere.io",
"name": "chronos",
"packageSource": "https://github.com/mesosphere/universe/archive/\
cli-tests.zip",
cli-test-2.zip",
"postInstallNotes": "Chronos DCOS Service has been successfully "
"installed!\n\n\tDocumentation: http://mesos."
"github.io/chronos\n\tIssues: https://github.com/"
@@ -64,13 +64,14 @@ cli-tests.zip",
"preInstallNotes": "We recommend a minimum of one node with at least "
"1 CPU and 2GB of RAM available for the Chronos "
"Service.",
"releaseVersion": "0",
"releaseVersion": "1",
"scm": "https://github.com/mesos/chronos.git",
"tags": [
"mesosphere",
"framework"
"cron",
"analytics",
"batch"
],
"version": "2.3.4"
"version": "2.4.0"
}]
return (json.dumps(result, sort_keys=True, indent=2).replace(' \n', '\n') +
@@ -96,8 +97,8 @@ def test_version():
def test_sources_list():
stdout = b"7b77ff84c23ffc575870c1eade68a28767ce0291 " + \
b"https://github.com/mesosphere/universe/archive/cli-tests.zip\n"
stdout = b"1a9bef0c579dd0692af9c6ba22c3ec910fb03efc " + \
b"https://github.com/mesosphere/universe/archive/cli-test-2.zip\n"
assert_command(['dcos', 'package', 'sources'],
stdout=stdout)
@@ -146,8 +147,8 @@ def test_describe():
def test_describe_cli():
stdout = file_json(
'tests/data/package/json/test_describe_cli_helloworld.json')
assert_command(['dcos', 'package', 'describe', 'helloworld', '--cli'],
'tests/data/package/json/test_describe_cli_cassandra.json')
assert_command(['dcos', 'package', 'describe', 'cassandra', '--cli'],
stdout=stdout)
@@ -219,7 +220,7 @@ def test_describe_app_cli():
stdout = file_bytes(
'tests/data/package/json/test_describe_app_cli.json')
assert_command(
['dcos', 'package', 'describe', 'helloworld', '--app', '--cli'],
['dcos', 'package', 'describe', 'cassandra', '--app', '--cli'],
stdout=stdout)
@@ -298,9 +299,9 @@ def test_install_specific_version():
def test_install_bad_package_version():
stderr = b'Version a.b.c of package [helloworld] is not available\n'
stderr = b'Version a.b.c of package [cassandra] is not available\n'
assert_command(
['dcos', 'package', 'install', 'helloworld',
['dcos', 'package', 'install', 'cassandra',
'--package-version=a.b.c'],
returncode=1,
stderr=stderr)
@@ -323,7 +324,7 @@ b3NwaGVyZS9kY29zLWhlbGxvd29ybGQifQ=="""
CJdfQ=="""
expected_source = b"""https://github.com/mesosphere/universe/archive/\
cli-tests.zip"""
cli-test-2.zip"""
expected_labels = {
'DCOS_PACKAGE_METADATA': expected_metadata,
@@ -375,12 +376,12 @@ cli-tests.zip"""
def test_install_with_id(zk_znode):
args = ['--app-id=chronos-1', '--yes']
stdout = (b'Installing Marathon app for package [chronos] version [2.3.4] '
stdout = (b'Installing Marathon app for package [chronos] version [2.4.0] '
b'with app id [chronos-1]\n')
_install_chronos(args=args, stdout=stdout)
args = ['--app-id=chronos-2', '--yes']
stdout = (b'Installing Marathon app for package [chronos] version [2.3.4] '
stdout = (b'Installing Marathon app for package [chronos] version [2.4.0] '
b'with app id [chronos-2]\n')
_install_chronos(args=args, stdout=stdout)
@@ -433,7 +434,7 @@ def test_uninstall_cli():
"maintainer": "support@mesosphere.io",
"name": "helloworld",
"packageSource": "https://github.com/mesosphere/universe/archive/\
cli-tests.zip",
cli-test-2.zip",
"postInstallNotes": "A sample post-installation message",
"preInstallNotes": "A sample pre-installation message",
"releaseVersion": "0",
@@ -551,7 +552,7 @@ def test_list_cli():
"maintainer": "support@mesosphere.io",
"name": "helloworld",
"packageSource": "https://github.com/mesosphere/universe/archive/\
cli-tests.zip",
cli-test-2.zip",
"postInstallNotes": "A sample post-installation message",
"preInstallNotes": "A sample pre-installation message",
"releaseVersion": "0",
@@ -585,7 +586,7 @@ cli-tests.zip",
"maintainer": "support@mesosphere.io",
"name": "helloworld",
"packageSource": "https://github.com/mesosphere/universe/archive/\
cli-tests.zip",
cli-test-2.zip",
"postInstallNotes": "A sample post-installation message",
"preInstallNotes": "A sample pre-installation message",
"releaseVersion": "0",
@@ -623,7 +624,7 @@ def test_uninstall_multiple_frameworknames(zk_znode):
_uninstall_chronos(
args=['--app-id=chronos-user-1'],
returncode=1,
stderr='Uninstalled package [chronos] version [2.3.4]\n'
stderr='Uninstalled package [chronos] version [2.4.0]\n'
'The Chronos DCOS Service has been uninstalled and will no '
'longer run.\nPlease follow the instructions at http://docs.'
'mesosphere.com/services/chronos/#uninstall to clean up any '
@@ -633,7 +634,7 @@ def test_uninstall_multiple_frameworknames(zk_znode):
_uninstall_chronos(
args=['--app-id=chronos-user-2'],
returncode=1,
stderr='Uninstalled package [chronos] version [2.3.4]\n'
stderr='Uninstalled package [chronos] version [2.4.0]\n'
'The Chronos DCOS Service has been uninstalled and will no '
'longer run.\nPlease follow the instructions at http://docs.'
'mesosphere.com/services/chronos/#uninstall to clean up any '
@@ -648,7 +649,7 @@ def test_uninstall_multiple_frameworknames(zk_znode):
def test_search():
returncode, stdout, stderr = exec_command(
['dcos', 'package', 'search', 'framework', '--json'])
['dcos', 'package', 'search', 'cron', '--json'])
assert returncode == 0
assert b'chronos' in stdout
@@ -660,7 +661,7 @@ def test_search():
assert returncode == 0
assert b'"packages": []' in stdout
assert b'"source": "https://github.com/mesosphere/universe/archive/\
cli-tests.zip"' in stdout
cli-test-2.zip"' in stdout
assert stderr == b''
returncode, stdout, stderr = exec_command(
@@ -704,7 +705,7 @@ def test_search_ends_with_wildcard():
registries = json.loads(stdout.decode('utf-8'))
for registry in registries:
assert len(registry['packages']) == 3
assert len(registry['packages']) == 2
def test_search_start_with_wildcard():
@@ -802,7 +803,7 @@ def _install_chronos(
args=['--yes'],
returncode=0,
stdout=b'Installing Marathon app for package [chronos] '
b'version [2.3.4]\n',
b'version [2.4.0]\n',
stderr=b'',
preInstallNotes=b'We recommend a minimum of one node with at least 1 '
b'CPU and 2GB of RAM available for the Chronos '

View File

@@ -5,11 +5,14 @@ import time
import dcos.util as util
from dcos.util import create_schema
import pytest
from ..fixtures.service import framework_fixture
from .common import (assert_command, assert_lines, delete_zk_node,
delete_zk_nodes, exec_command, get_services,
package_install, package_uninstall, service_shutdown,
ssh_output, watch_all_deployments)
package_install, package_uninstall, remove_app,
service_shutdown, ssh_output, wait_for_service,
watch_all_deployments)
def setup_module(module):
@@ -19,7 +22,7 @@ def setup_module(module):
def teardown_module(module):
package_uninstall(
'chronos',
stderr=b'Uninstalled package [chronos] version [2.3.4]\n'
stderr=b'Uninstalled package [chronos] version [2.4.0]\n'
b'The Chronos DCOS Service has been uninstalled and will no '
b'longer run.\nPlease follow the instructions at http://docs.'
b'mesosphere.com/services/chronos/#uninstall to clean up any '
@@ -86,6 +89,8 @@ def test_service_inactive():
delete_zk_node('cassandra-mesos')
package_uninstall('cassandra')
def test_service_completed():
package_install('cassandra', True)
@@ -116,6 +121,8 @@ def test_service_completed():
assert len(services) >= 3
assert any(service['id'] == cassandra_id for service in services)
package_uninstall('cassandra')
def test_log():
returncode, stdout, stderr = exec_command(
@@ -174,10 +181,12 @@ def test_log_config():
def test_log_follow():
wait_for_service('chronos')
proc = subprocess.Popen(['dcos', 'service', 'log', 'chronos', '--follow'],
preexec_fn=os.setsid,
stdout=subprocess.PIPE)
time.sleep(3)
time.sleep(5)
proc.poll()
assert proc.returncode is None
@@ -190,11 +199,17 @@ def test_log_lines():
assert_lines(['dcos', 'service', 'log', 'chronos', '--lines=4'], 4)
@pytest.mark.skipif(True, reason='Broken Marathon but we need to release')
def test_log_multiple_apps():
package_install('marathon', True)
package_install('marathon', True,
package_install('marathon',
True,
['--options=tests/data/service/marathon-user.json'])
package_install('marathon',
True,
['--options=tests/data/service/marathon-user2.json',
'--app-id=marathon-user2'])
wait_for_service('marathon-user', number_of_services=2)
try:
stderr = (b'Multiple marathon apps found for service name ' +
b'[marathon-user]: [/marathon-user], [/marathon-user2]\n')
@@ -202,20 +217,15 @@ def test_log_multiple_apps():
returncode=1,
stderr=stderr)
finally:
# Uninstall notes and message are printed twice because --all will
# uninstall two packages
package_uninstall(
'marathon', ['--all'],
stderr=b'Uninstalled package [marathon] version [0.9.0]\n'
b'The Marathon DCOS Service has been uninstalled and will '
b'no longer run.\nPlease follow the instructions at http://'
b'docs.mesosphere.com/services/marathon/#uninstall to '
b'clean up any persisted state\n'
b'Uninstalled package [marathon] version [0.9.0]\n'
b'The Marathon DCOS Service has been uninstalled and will '
b'no longer run.\nPlease follow the instructions at http://'
b'docs.mesosphere.com/services/marathon/#uninstall to '
b'clean up any persisted state\n')
# We can't use `dcos package uninstall`. The services have the same
# name. Manually remove the dcos services.
remove_app('marathon-user')
remove_app('marathon-user2')
for service in get_services():
if service['name'] == 'marathon-user':
service_shutdown(service['id'])
delete_zk_node('universe')
def test_log_no_apps():

View File

@@ -34,11 +34,11 @@ NUM_TASKS = len(INIT_APPS)
def setup_module():
# create a completed task
with app(SLEEP_COMPLETED, 'test-app-completed', True):
with app(SLEEP_COMPLETED, 'test-app-completed'):
pass
for app_ in INIT_APPS:
add_app(app_[0], True)
add_app(app_[0])
def teardown_module():
@@ -170,7 +170,7 @@ def test_log_lines_invalid():
def test_log_follow():
""" Test --follow """
# verify output
with app(FOLLOW, 'follow', True):
with app(FOLLOW, 'follow'):
proc = subprocess.Popen(['dcos', 'task', 'log', 'follow', '--follow'],
stdout=subprocess.PIPE)
@@ -205,7 +205,7 @@ def test_log_two_tasks():
def test_log_two_tasks_follow():
""" Test tailing a single file on two separate tasks with --follow """
with app(TWO_TASKS_FOLLOW, 'two-tasks-follow', True):
with app(TWO_TASKS_FOLLOW, 'two-tasks-follow'):
proc = subprocess.Popen(
['dcos', 'task', 'log', 'two-tasks-follow', '--follow'],
stdout=subprocess.PIPE)

View File

@@ -1,64 +0,0 @@
import collections
import json
import pkg_resources
from dcos import util
from dcoscli.marathon import main
import pytest
ResourceData = collections.namedtuple(
'ResourceData',
['properties', 'expected'])
def get_clean_app_response():
return json.loads(
pkg_resources.resource_string(
'tests',
'data/marathon/apps/cleaned_response.json').decode('utf-8'))
@pytest.fixture(params=[
ResourceData(
properties=json.loads(
pkg_resources.resource_string(
'tests',
'data/marathon/apps/response.json').decode('utf-8')),
expected=get_clean_app_response()
)])
def resource_data(request):
return request.param
def test_clean_up_resource_definition(resource_data):
result = main._clean_up_resource_definition(resource_data.properties)
assert result == resource_data.expected
schema = main._data_schema()
ValidationCheck = collections.namedtuple(
'ValidationCheck',
['properties', 'expected'])
@pytest.fixture(params=[
ValidationCheck(
properties=get_clean_app_response(),
expected=[]),
ValidationCheck(
properties=json.loads(
pkg_resources.resource_string(
'tests',
'data/marathon/groups/complicated.json').decode('utf-8')),
expected=[])
])
def validation_check(request):
return request.param
def test_validation(validation_check):
result = util.validate_json(validation_check.properties, schema)
assert result == validation_check.expected

View File

@@ -23,16 +23,16 @@ commands =
[testenv:py27-integration]
commands =
py.test -vv -x {env:CI_FLAGS:} tests/integrations{posargs}
py.test -p no:cacheprovider -vv -x {env:CI_FLAGS:} tests/integrations{posargs}
[testenv:py34-integration]
commands =
py.test -vv -x {env:CI_FLAGS:} tests/integrations{posargs}
py.test -p no:cacheprovider -vv -x {env:CI_FLAGS:} tests/integrations{posargs}
[testenv:py27-unit]
commands =
py.test -vv {env:CI_FLAGS:} tests/unit{posargs}
py.test -p no:cacheprovider -vv {env:CI_FLAGS:} tests/unit{posargs}
[testenv:py34-unit]
commands =
py.test -vv {env:CI_FLAGS:} tests/unit{posargs}
py.test -p no:cacheprovider -vv {env:CI_FLAGS:} tests/unit{posargs}

View File

@@ -9,7 +9,7 @@ logger = util.get_logger(__name__)
def parse_json_item(json_item, schema):
"""Parse the json item based on a schema.
"""Parse the json item (optionally based on a schema).
:param json_item: A JSON item in the form 'key=value'
:type json_item: str
@@ -25,7 +25,13 @@ def parse_json_item(json_item, schema):
# Check that it is a valid key in our jsonschema
key = terms[0]
value = parse_json_value(key, terms[1], schema)
# Use the schema if we have it else, guess the type
if schema:
value = parse_json_value(key, terms[1], schema)
else:
value = _find_type(clean_value(terms[1]))
return (json.dumps(key), value)
@@ -121,6 +127,26 @@ def clean_value(value):
return value
def _find_type(value):
"""Find the correct type of the value
:param value: value to parse
:type value: str
:returns: The parsed value
:rtype: int|float|
"""
to_try = [_parse_integer, _parse_number, _parse_boolean, _parse_array,
_parse_url, _parse_object, _parse_string]
while len(to_try) > 0:
try:
return to_try.pop(0)(value)
except DCOSException:
pass
raise DCOSException(
'Unable to parse {!r} as a JSON object'.format(value))
def _parse_string(value):
"""
:param value: The string to parse

View File

@@ -19,8 +19,8 @@ commands =
[testenv:py27-unit]
commands =
py.test -vv {env:CI_FLAGS:} --cov {envsitepackagesdir}/dcos tests
py.test -p no:cacheprovider -vv {env:CI_FLAGS:} --cov {envsitepackagesdir}/dcos tests
[testenv:py34-unit]
commands =
py.test -vv {env:CI_FLAGS:} --cov {envsitepackagesdir}/dcos tests
py.test -p no:cacheprovider -vv {env:CI_FLAGS:} --cov {envsitepackagesdir}/dcos tests