from datetime import datetime
from ..exceptions import InvalidChoiceError
from .base import MarathonResource, MarathonObject, assert_valid_path
from .constraint import MarathonConstraint
from .container import MarathonContainer
from .deployment import MarathonDeployment
from .task import MarathonTask
[docs]class MarathonApp(MarathonResource):
"""Marathon Application resource.
See: https://mesosphere.github.io/marathon/docs/rest-api.html#post-/v2/apps
:param list[str] accepted_resource_roles: a list of resource roles (the resource offer
must contain at least one of these for the app
to be launched on that host)
:param list[str] args: args form of the command to run
:param int backoff_factor: multiplier for subsequent backoff
:param int backoff_seconds: base time, in seconds, for exponential backoff
:param str cmd: cmd form of the command to run
:param constraints: placement constraints
:type constraints: list[:class:`marathon.models.constraint.MarathonConstraint`] or list[tuple]
:param container: container info
:type container: :class:`marathon.models.container.MarathonContainer` or dict
:param float cpus: cpus required per instance
:param list[str] dependencies: services (app IDs) on which this app depends
:param int disk: disk required per instance
:param deployments: (read-only) currently running deployments that affect this app
:type deployments: list[:class:`marathon.models.deployment.MarathonDeployment`]
:param dict env: env vars
:param str executor: executor
:param int gpus: gpus required per instance
:param health_checks: health checks
:type health_checks: list[:class:`marathon.models.MarathonHealthCheck`] or list[dict]
:param str id: app id
:param int instances: instances
:param last_task_failure: last task failure
:type last_task_failure: :class:`marathon.models.app.MarathonTaskFailure` or dict
:param float mem: memory (in MB) required per instance
:param dict secrets: A map with named secret declarations.
:type port_definitions: list[:class:`marathon.models.app.PortDefinitions`] or list[dict]
:param list[int] ports: ports
:param bool require_ports: require the specified `ports` to be available in the resource offer
:param list[str] store_urls: store URLs
:param float task_rate_limit: (Removed in Marathon 0.7.0) maximum number of tasks launched per second
:param tasks: (read-only) tasks
:type tasks: list[:class:`marathon.models.task.MarathonTask`]
:param int tasks_running: (read-only) the number of running tasks
:param int tasks_staged: (read-only) the number of staged tasks
:param int tasks_healthy: (read-only) the number of healthy tasks
:param int tasks_unhealthy: (read-only) the number of unhealthy tasks
:param upgrade_strategy: strategy by which app instances are replaced during a deployment
:type upgrade_strategy: :class:`marathon.models.app.MarathonUpgradeStrategy` or dict
:param list[str] uris: uris
:param str user: user
:param str version: version id
:param version_info: time of last scaling, last config change
:type version_info: :class:`marathon.models.app.MarathonAppVersionInfo` or dict
:param task_stats: task statistics
:type task_stats: :class:`marathon.models.app.MarathonTaskStats` or dict
:param dict labels
:type readiness_checks: list[:class:`marathon.models.app.ReadinessCheck`] or list[dict]
:type residency: :class:`marathon.models.app.Residency` or dict
:param int task_kill_grace_period_seconds: Configures the termination signal escalation behavior of executors when stopping tasks.
:param list[dict] unreachable_strategy: Handling for unreachable instances.
:param str kill_selection: Defines which instance should be killed first in case of e.g. rescaling.
"""
UPDATE_OK_ATTRIBUTES = [
'args', 'backoff_factor', 'backoff_seconds', 'cmd', 'constraints', 'container', 'cpus', 'dependencies', 'disk',
'env', 'executor', 'gpus', 'health_checks', 'instances', 'kill_selection', 'labels', 'max_launch_delay_seconds',
'mem', 'ports', 'require_ports', 'store_urls', 'task_rate_limit', 'upgrade_strategy', 'unreachable_strategy',
'uris', 'user', 'version'
]
"""List of attributes which may be updated/changed after app creation"""
CREATE_ONLY_ATTRIBUTES = ['id', 'accepted_resource_roles']
"""List of attributes that should only be passed on creation"""
READ_ONLY_ATTRIBUTES = [
'deployments', 'tasks', 'tasks_running', 'tasks_staged', 'tasks_healthy', 'tasks_unhealthy']
"""List of read-only attributes"""
KILL_SELECTIONS = ["YOUNGEST_FIRST", "OLDEST_FIRST"]
def __init__(self, accepted_resource_roles=None, args=None, backoff_factor=None, backoff_seconds=None, cmd=None,
constraints=None, container=None, cpus=None, dependencies=None, deployments=None, disk=None, env=None,
executor=None, health_checks=None, id=None, instances=None, kill_selection=None, labels=None,
last_task_failure=None, max_launch_delay_seconds=None, mem=None, ports=None, require_ports=None,
store_urls=None, task_rate_limit=None, tasks=None, tasks_running=None, tasks_staged=None,
tasks_healthy=None, task_kill_grace_period_seconds=None, tasks_unhealthy=None, upgrade_strategy=None,
unreachable_strategy=None, uris=None, user=None, version=None, version_info=None,
ip_address=None, fetch=None, task_stats=None, readiness_checks=None,
readiness_check_results=None, secrets=None, port_definitions=None, residency=None, gpus=None, networks=None):
# self.args = args or []
self.accepted_resource_roles = accepted_resource_roles
self.args = args
# Marathon 0.7.0-RC1 throws a validation error if this is [] and cmd is passed:
# "error": "AppDefinition must either contain a 'cmd' or a 'container'."
self.backoff_factor = backoff_factor
self.backoff_seconds = backoff_seconds
self.cmd = cmd
self.constraints = [
c if isinstance(c, MarathonConstraint) else MarathonConstraint(*c)
for c in (constraints or [])
]
self.container = container if (isinstance(container, MarathonContainer) or container is None) \
else MarathonContainer.from_json(container)
self.cpus = cpus
self.dependencies = dependencies or []
self.deployments = [
d if isinstance(
d, MarathonDeployment) else MarathonDeployment().from_json(d)
for d in (deployments or [])
]
self.disk = disk
self.env = env or dict()
self.executor = executor
self.gpus = gpus
self.health_checks = health_checks or []
self.health_checks = [
hc if isinstance(
hc, MarathonHealthCheck) else MarathonHealthCheck().from_json(hc)
for hc in (health_checks or [])
]
self.id = assert_valid_path(id)
self.instances = instances
if kill_selection and kill_selection not in self.KILL_SELECTIONS:
raise InvalidChoiceError(
'kill_selection', kill_selection, self.KILL_SELECTIONS)
self.kill_selection = kill_selection
self.labels = labels or {}
self.last_task_failure = last_task_failure if (isinstance(last_task_failure, MarathonTaskFailure) or last_task_failure is None) \
else MarathonTaskFailure.from_json(last_task_failure)
self.max_launch_delay_seconds = max_launch_delay_seconds
self.mem = mem
self.ports = ports or []
self.port_definitions = [
pd if isinstance(
pd, PortDefinition) else PortDefinition.from_json(pd)
for pd in (port_definitions or [])
]
self.readiness_checks = [
rc if isinstance(
rc, ReadinessCheck) else ReadinessCheck().from_json(rc)
for rc in (readiness_checks or [])
]
self.readiness_check_results = readiness_check_results or []
self.residency = residency
self.require_ports = require_ports
self.secrets = secrets or {}
for k, s in self.secrets.items():
if not isinstance(s, Secret):
self.secrets[k] = Secret().from_json(s)
self.store_urls = store_urls or []
self.task_rate_limit = task_rate_limit
self.tasks = [
t if isinstance(t, MarathonTask) else MarathonTask().from_json(t)
for t in (tasks or [])
]
self.tasks_running = tasks_running
self.tasks_staged = tasks_staged
self.tasks_healthy = tasks_healthy
self.task_kill_grace_period_seconds = task_kill_grace_period_seconds
self.tasks_unhealthy = tasks_unhealthy
self.upgrade_strategy = upgrade_strategy if (isinstance(upgrade_strategy, MarathonUpgradeStrategy) or upgrade_strategy is None) \
else MarathonUpgradeStrategy.from_json(upgrade_strategy)
self.unreachable_strategy = unreachable_strategy \
if (isinstance(unreachable_strategy, MarathonUnreachableStrategy)
or unreachable_strategy is None) \
else MarathonUnreachableStrategy.from_json(unreachable_strategy)
self.uris = uris or []
self.fetch = fetch or []
self.user = user
self.version = version
self.version_info = version_info if (isinstance(version_info, MarathonAppVersionInfo) or version_info is None) \
else MarathonAppVersionInfo.from_json(version_info)
self.task_stats = task_stats if (isinstance(task_stats, MarathonTaskStats) or task_stats is None) \
else MarathonTaskStats.from_json(task_stats)
self.networks = networks
[docs] def add_env(self, key, value):
self.env[key] = value
[docs]class MarathonHealthCheck(MarathonObject):
"""Marathon health check.
See https://mesosphere.github.io/marathon/docs/health-checks.html
:param str command: health check command (if protocol == 'COMMAND')
:param int grace_period_seconds: how long to ignore health check failures on initial task launch (before first healthy status)
:param int interval_seconds: how long to wait between health checks
:param int max_consecutive_failures: max number of consecutive failures before the task should be killed
:param str path: health check target path (if protocol == 'HTTP')
:param int port_index: target port as indexed in app's `ports` array
:param str protocol: health check protocol ('HTTP', 'TCP', or 'COMMAND')
:param int timeout_seconds: how long before a waiting health check is considered failed
:param bool ignore_http1xx: Ignore HTTP informational status codes 100 to 199.
:param dict kwargs: additional arguments for forward compatibility
"""
def __init__(self, command=None, grace_period_seconds=None, interval_seconds=None, max_consecutive_failures=None,
path=None, port_index=None, protocol=None, timeout_seconds=None, ignore_http1xx=None, **kwargs):
self.command = command
self.grace_period_seconds = grace_period_seconds
self.interval_seconds = interval_seconds
self.max_consecutive_failures = max_consecutive_failures
self.path = path
self.port_index = port_index
self.protocol = protocol
self.timeout_seconds = timeout_seconds
self.ignore_http1xx = ignore_http1xx
# additional not previously known healthcheck attributes
for k, v in kwargs.items():
setattr(self, k, v)
[docs]class MarathonTaskFailure(MarathonObject):
"""Marathon Task Failure.
:param str app_id: application id
:param str host: mesos slave running the task
:param str message: error message
:param str task_id: task id
:param str instance_id: instance id
:param str state: task state
:param timestamp: when this task failed
:type timestamp: datetime or str
:param str version: app version with which this task was started
"""
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
def __init__(self, app_id=None, host=None, message=None, task_id=None, instance_id=None,
slave_id=None, state=None, timestamp=None, version=None):
self.app_id = app_id
self.host = host
self.message = message
self.task_id = task_id
self.instance_id = instance_id
self.slave_id = slave_id
self.state = state
self.timestamp = timestamp if (timestamp is None or isinstance(timestamp, datetime)) \
else datetime.strptime(timestamp, self.DATETIME_FORMAT)
self.version = version
[docs]class MarathonUpgradeStrategy(MarathonObject):
"""Marathon health check.
See https://mesosphere.github.io/marathon/docs/health-checks.html
:param float minimum_health_capacity: minimum % of instances kept healthy on deploy
"""
def __init__(self, maximum_over_capacity=None,
minimum_health_capacity=None):
self.maximum_over_capacity = maximum_over_capacity
self.minimum_health_capacity = minimum_health_capacity
[docs]class MarathonUnreachableStrategy(MarathonObject):
"""Marathon unreachable Strategy.
Define handling for unreachable instances. Given
`unreachable_inactive_after_seconds = 60` and
`unreachable_expunge_after = 120`, an instance will be expunged if it has
been unreachable for more than 120 seconds or a second instance is started
if it has been unreachable for more than 60 seconds.",
See https://mesosphere.github.io/marathon/docs/?
:param int unreachable_inactive_after_seconds: time an instance is
unreachable for in seconds before marked as inactive.
:param int unreachable_expunge_after_seconds: time an instance is
unreachable for in seconds before expunged.
:param int inactive_after_seconds
:param int expunge_after_seconds
"""
DISABLED = 'disabled'
def __init__(self, unreachable_inactive_after_seconds=None,
unreachable_expunge_after_seconds=None,
inactive_after_seconds=None, expunge_after_seconds=None):
self.unreachable_inactive_after_seconds = unreachable_inactive_after_seconds
self.unreachable_expunge_after_seconds = unreachable_expunge_after_seconds
self.inactive_after_seconds = inactive_after_seconds
self.expunge_after_seconds = expunge_after_seconds
@classmethod
[docs] def from_json(cls, attributes):
if attributes == cls.DISABLED:
return cls.DISABLED
return super(MarathonUnreachableStrategy, cls).from_json(attributes)
[docs]class MarathonAppVersionInfo(MarathonObject):
"""Marathon App version info.
See release notes for Marathon v0.11.0
https://github.com/mesosphere/marathon/releases/tag/v0.11.0
:param str app_id: application id
:param str host: mesos slave running the task
"""
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
def __init__(self, last_scaling_at=None, last_config_change_at=None):
self.last_scaling_at = self._to_datetime(last_scaling_at)
self.last_config_change_at = self._to_datetime(last_config_change_at)
def _to_datetime(self, timestamp):
if (timestamp is None or isinstance(timestamp, datetime)):
return timestamp
else:
return datetime.strptime(timestamp, self.DATETIME_FORMAT)
[docs]class MarathonTaskStats(MarathonObject):
"""Marathon task statistics
See https://mesosphere.github.io/marathon/docs/rest-api.html#taskstats-object-v0-11
:param started_after_last_scaling: contains statistics about all tasks that were started after the last scaling or restart operation.
:type started_after_last_scaling: :class:`marathon.models.app.MarathonTaskStatsType` or dict
:param with_latest_config: contains statistics about all tasks that run with the same config as the latest app version.
:type with_latest_config: :class:`marathon.models.app.MarathonTaskStatsType` or dict
:param with_outdated_config: contains statistics about all tasks that were started before the last config change
which was not simply a restart or scaling operation.
:type with_outdated_config: :class:`marathon.models.app.MarathonTaskStatsType` or dict
:param total_summary: contains statistics about all tasks.
:type total_summary: :class:`marathon.models.app.MarathonTaskStatsType` or dict
"""
def __init__(self, started_after_last_scaling=None,
with_latest_config=None, with_outdated_config=None, total_summary=None):
self.started_after_last_scaling = started_after_last_scaling if \
(isinstance(started_after_last_scaling, MarathonTaskStatsType) or started_after_last_scaling is None) \
else MarathonTaskStatsType.from_json(started_after_last_scaling)
self.with_latest_config = with_latest_config if \
(isinstance(with_latest_config, MarathonTaskStatsType) or with_latest_config is None) \
else MarathonTaskStatsType.from_json(with_latest_config)
self.with_outdated_config = with_outdated_config if \
(isinstance(with_outdated_config, MarathonTaskStatsType) or with_outdated_config is None) \
else MarathonTaskStatsType.from_json(with_outdated_config)
self.total_summary = total_summary if \
(isinstance(total_summary, MarathonTaskStatsType) or total_summary is None) \
else MarathonTaskStatsType.from_json(total_summary)
[docs]class MarathonTaskStatsType(MarathonObject):
"""Marathon app task stats
:param stats: stast about app tasks
:type stats: :class:`marathon.models.app.MarathonTaskStatsStats` or dict
"""
def __init__(self, stats=None):
self.stats = stats if (isinstance(stats, MarathonTaskStatsStats) or stats is None)\
else MarathonTaskStatsStats.from_json(stats)
[docs]class MarathonTaskStatsStats(MarathonObject):
"""Marathon app task stats
:param counts: app task count breakdown
:type counts: :class:`marathon.models.app.MarathonTaskStatsCounts` or dict
:param life_time: app task life time stats
:type life_time: :class:`marathon.models.app.MarathonTaskStatsLifeTime` or dict
"""
def __init__(self, counts=None, life_time=None):
self.counts = counts if (isinstance(counts, MarathonTaskStatsCounts) or counts is None)\
else MarathonTaskStatsCounts.from_json(counts)
self.life_time = life_time if (isinstance(life_time, MarathonTaskStatsLifeTime) or life_time is None)\
else MarathonTaskStatsLifeTime.from_json(life_time)
[docs]class MarathonTaskStatsCounts(MarathonObject):
"""Marathon app task counts
Equivalent to tasksStaged, tasksRunning, tasksHealthy, tasksUnhealthy.
:param int staged: Staged task count
:param int running: Running task count
:param int healthy: Healthy task count
:param int unhealthy: unhealthy task count
"""
def __init__(self, staged=None,
running=None, healthy=None, unhealthy=None):
self.staged = staged
self.running = running
self.healthy = healthy
self.unhealthy = unhealthy
[docs]class MarathonTaskStatsLifeTime(MarathonObject):
"""Marathon app life time statistics
Measured from `"startedAt"` (timestamp of the Mesos TASK_RUNNING status update) of each running task until now
:param float average_seconds: Average seconds
:param float median_seconds: Median seconds
"""
def __init__(self, average_seconds=None, median_seconds=None):
self.average_seconds = average_seconds
self.median_seconds = median_seconds
[docs]class ReadinessCheck(MarathonObject):
"""Marathon readiness check: https://mesosphere.github.io/marathon/docs/readiness-checks.html
:param string name (Optional. Default: "readinessCheck"): The name used to identify this readiness check.
:param string protocol (Optional. Default: "HTTP"): Protocol of the requests to be performed. Either HTTP or HTTPS.
:param string path (Optional. Default: "/"): Path to the endpoint the task exposes to provide readiness status.
Example: /path/to/readiness.
:param string port_name (Optional. Default: "http-api"): Name of the port to query as described in the
portDefinitions. Example: http-api.
:param int interval_seconds (Optional. Default: 30 seconds): Number of seconds to wait between readiness checks.
:param int timeout_seconds (Optional. Default: 10 seconds): Number of seconds after which a readiness check
times out, regardless of the response. This value must be smaller than interval_seconds.
:param list http_status_codes_for_ready (Optional. Default: [200]): The HTTP/HTTPS status code to treat as ready.
:param bool preserve_last_response (Optional. Default: false): If true, the last readiness check response will be
preserved and exposed in the API as part of a deployment.
"""
def __init__(self, name=None, protocol=None, path=None, port_name=None, interval_seconds=None,
http_status_codes_for_ready=None, preserve_last_response=None, timeout_seconds=None):
self.name = name
self.protocol = protocol
self.path = path
self.port_name = port_name
self.interval_seconds = interval_seconds
self.http_status_codes_for_ready = http_status_codes_for_ready
self.preserve_last_response = preserve_last_response
self.timeout_seconds = timeout_seconds
[docs]class PortDefinition(MarathonObject):
"""Marathon port definitions: https://mesosphere.github.io/marathon/docs/ports.html
:param int port: The port
:param string protocol: tcp or udp
:param string name: (optional) the name of the port
:param dict labels: undocumented
"""
def __init__(self, port=None, protocol=None, name=None, labels=None):
self.port = port
self.protocol = protocol
self.name = name
self.labels = labels
[docs]class Residency(MarathonObject):
"""Declares how "resident" an app is: https://mesosphere.github.io/marathon/docs/persistent-volumes.html
:param int relaunch_escalation_timeout_seconds: How long marathon will try to relaunch where the volumes is, defaults to 3600
:param string task_lost_behavior: What to do after a TASK_LOST. See the official Marathon docs for options
"""
def __init__(self, relaunch_escalation_timeout_seconds=None, task_lost_behavior=None):
self.relaunch_escalation_timeout_seconds = relaunch_escalation_timeout_seconds
self.task_lost_behavior = task_lost_behavior
[docs]class Secret(MarathonObject):
"""Declares marathon secret object.
:param str source: The source of the secret's value. The format depends on the secret store used by Mesos.
"""
def __init__(self, source=None):
self.source = source