Commit b715597d authored by Daniele Venzano's avatar Daniele Venzano

Modify the resources fields

parent 3ef5878e
......@@ -18,6 +18,7 @@
"42"
]
],
"volumes": [],
"essential_count": 1,
"monitor": false,
"name": "spark-master",
......@@ -29,11 +30,20 @@
"protocol": "tcp"
}
],
"required_resources": {
"memory": 536870912
"resources": {
"memory": {
"min": 536870912,
"max": 536870912
},
"cores": {
"min": null,
"max": null
}
},
"startup_order": 0,
"total_count": 1
"total_count": 1,
"replicas": 1,
"command": null
},
{
"image": "docker-registry:5000/zapps/spark2-worker",
......@@ -74,11 +84,21 @@
"protocol": "tcp"
}
],
"required_resources": {
"memory": 12884901888
"resources": {
"memory": {
"min": 12884901888,
"max": 12884901888
},
"cores": {
"min": null,
"max": null
}
},
"volumes": [],
"startup_order": 1,
"total_count": 2
"total_count": 2,
"replicas": 1,
"command": null
},
{
"image": "docker-registry:5000/zapps/spark2-jupyter-notebook",
......@@ -112,6 +132,7 @@
"hdfs-namenode.zoe"
]
],
"volumes": [],
"essential_count": 1,
"monitor": true,
"name": "spark-jupyter",
......@@ -129,11 +150,20 @@
"protocol": "tcp"
}
],
"required_resources": {
"memory": 4294967296
"resources": {
"memory": {
"min": 4294967296,
"max": 4294967296
},
"cores": {
"min": null,
"max": null
}
},
"startup_order": 0,
"total_count": 1
"total_count": 1,
"replicas": 1,
"command": null
}
],
"version": 3,
......
......@@ -198,12 +198,7 @@ Will return a JSON document like this::
"error_message" : null,
"id" : 26774,
"description" : {
"required_resources" : {
"memory" : 536870912
},
[...]
"name" : "boinc-client",
"volumes" : []
}
}
......
......@@ -125,12 +125,16 @@ number <= total_count
The minimum number of services of this type that Zoe must start before being able to consider the ZApp as started. For example, in Spark you need just one worker to produce useful work (essential_count equal to 1), but if there is the possibility of adding up to 9 more workers, the application will run faster (total_count equal to 10).
required_resources
^^^^^^^^^^^^^^^^^^
resources
^^^^^^^^^
object
Resources that need to be reserved for this service. Currently only ``memory`` is supported, specified in bytes.
Resources that need to be reserved for this service. Each resource is specified as a minimum and a maximum. The application is started if the minimum quantity of resources is available in the systems and it is killed if it passes over the maximum limit. If minimum and maximum limits are specified as ``null``, they will be ignored.
``cores`` and ``memory`` are the resources currently supported.
Support for this feature depends on the scheduler and back-end in use.
startup_order
^^^^^^^^^^^^^
......@@ -198,8 +202,15 @@ Example
"monitor": true,
"total_count": 1,
"essential_count": 1,
"required_resources": {
"memory": 4294967296
"resources": {
"memory": {
"min": 4294967296,
"max": 4294967296
},
"cores": {
"min": null,
"max": null
}
},
"startup_order": 0,
"ports": [
......
......@@ -57,6 +57,11 @@
"multipleOf": 1.0,
"minimum": 1
},
"replicas": {
"type": "number",
"multipleOf": 1.0,
"minimum": 1
},
"resources": {
"type": "object",
"properties": {
......@@ -140,7 +145,7 @@
}
},
"additionalProperties": false,
"required": ["name", "image", "ports", "environment", "volumes", "monitor", "startup_order", "essential_count", "total_count", "resources", "command"]
"required": ["name", "image", "ports", "environment", "volumes", "monitor", "startup_order", "essential_count", "total_count", "resources", "command", "replicas"]
},
"port": {
"type": "object",
......
......@@ -44,8 +44,8 @@ export class ServiceDescription implements Serializable<ServiceDescription> {
this.version = input.version;
}
if (input.hasOwnProperty('required_resources')) {
this.requiredResources = new Resource().deserialize(input.required_resources);
if (input.hasOwnProperty('resources')) {
this.requiredResources = new Resource().deserialize(input.resources);
}
if (input.hasOwnProperty('environment')) {
......
......@@ -121,8 +121,8 @@ export class Service implements Serializable<Service> {
}
}
if (input.hasOwnProperty('required_resources')) {
this.requiredResources = new Resource().deserialize(input.required_resources);
if (input.hasOwnProperty('resources')) {
this.requiredResources = new Resource().deserialize(input.resources);
}
if (input.hasOwnProperty('startup_order')) {
......
......@@ -56,9 +56,9 @@ def app_validate(data):
if service['monitor']:
found_monitor = True
break
if service['resources']['memory']['min'] > service['resources']['memory']['max']:
if service['resources']['memory']['min'] is not None and service['resources']['memory']['min'] > service['resources']['memory']['max']:
raise InvalidApplicationDescription(msg='service {} has mismatching min and max memory limits'.format(service['name']))
if service['resources']['cores']['min'] > service['resources']['cores']['max']:
if service['resources']['cores']['min'] is not None and service['resources']['cores']['min'] > service['resources']['cores']['max']:
raise InvalidApplicationDescription(msg='service {} has mismatching min and max memory limits'.format(service['name']))
if not found_monitor:
......
......@@ -22,11 +22,33 @@ from zoe_lib.config import get_conf
log = logging.getLogger(__name__)
class ResourceLimits:
"""A resource limits description."""
def __init__(self, data, unit):
if isinstance(data, dict):
self.min = data['min']
self.max = data['max']
elif isinstance(data, ResourceLimits):
self.min = data.min
self.max = data.max
else:
raise TypeError
self.unit = unit
def __add__(self, other):
if isinstance(other, ResourceLimits) and self.unit == other.unit:
res = {
'min': self.min + other.min,
'max': self.max + other.max
}
return ResourceLimits(res, self.unit)
class ResourceReservation:
"""The resources reserved by a Service."""
def __init__(self, data):
self.memory = data['memory']
self.cores = data['cores']
self.memory = ResourceLimits(data['memory'], "bytes")
self.cores = ResourceLimits(data['cores'], 'units')
def __add__(self, other):
if isinstance(other, ResourceReservation):
......@@ -92,25 +114,15 @@ class Service:
self.essential = d['essential']
# Fields parsed from the JSON description
self.image_name = self.description['docker_image']
self.image_name = self.description['image']
self.is_monitor = self.description['monitor']
self.startup_order = self.description['startup_order']
self.environment = []
if 'environment' in self.description:
self.environment = self.description['environment']
self.command = ''
if 'command' in self.description:
self.command = self.description['command']
self.resource_reservation = ResourceReservation(self.description['required_resources'])
self.volumes = []
if 'volumes' in self.description:
self.volumes = [VolumeDescription(v) for v in self.description['volumes']]
self.environment = self.description['environment']
self.command = self.description['command']
self.resource_reservation = ResourceReservation(self.description['resources'])
self.volumes = [VolumeDescription(v) for v in self.description['volumes']]
self.ports = [ExposedPort(p) for p in self.description['ports']]
if 'replicas' in self.description:
self.replicas = self.description['replicas']
else:
self.replicas = 1
self.replicas = self.description['replicas']
def serialize(self):
"""Generates a dictionary that can be serialized in JSON."""
......
......@@ -243,10 +243,11 @@ class KubernetesClient:
if len(service_instance.ports) > 0:
config.set_spec_container_ports(service_instance.ports)
config.set_spec_container_mem_limit(service_instance.memory_limit)
if service_instance.memory_limit is not None:
config.set_spec_container_mem_limit(service_instance.memory_limit.max)
if service_instance.core_limit != 0:
config.set_spec_container_core_limit(service_instance.core_limit)
if service_instance.core_limit is not None:
config.set_spec_container_core_limit(service_instance.core_limit.max)
if len(service_instance.volumes) > 0:
config.set_spec_container_volumes(service_instance.volumes, service_instance.name)
......
......@@ -26,8 +26,15 @@ class ServiceInstance:
self.name = service.unique_name
self.hostname = service.dns_name
self.memory_limit = service.resource_reservation.memory
self.core_limit = service.resource_reservation.cores
if service.resource_reservation.memory.min is None:
self.memory_limit = None
else:
self.memory_limit = service.resource_reservation.memory
if service.resource_reservation.cores.min is None:
self.core_limit = None
else:
self.core_limit = service.resource_reservation.cores
self.labels = {
'zoe.execution.name': execution.name,
......
......@@ -187,6 +187,12 @@ class SwarmClient:
log.error('Swarm backend does not support volume type {}'.format(volume.type))
volumes[volume.path] = {'bind': volume.mount_point, 'mode': ("ro" if volume.readonly else "rw")}
if service_instance.memory_limit is not None:
mem_limit = service_instance.memory_limit.max
else:
mem_limit = 0
# Swarm backend does not support cores in a consistent way, see https://github.com/docker/swarm/issues/475
try:
cont = self.cli.containers.run(image=service_instance.image_name,
command=service_instance.command,
......@@ -195,8 +201,8 @@ class SwarmClient:
hostname=service_instance.hostname,
labels=service_instance.labels,
log_config=log_config,
mem_limit=service_instance.memory_limit,
memswap_limit=service_instance.memory_limit,
mem_limit=mem_limit,
memswap_limit=0,
name=service_instance.name,
networks=[get_conf().overlay_network_name],
network_disabled=False,
......
......@@ -19,7 +19,7 @@ class SimulatedNode:
def service_fits(self, service: Service) -> bool:
"""Checks whether a service can fit in this node"""
return service.resource_reservation.memory < self.node_free_memory()
return service.resource_reservation.memory.min < self.node_free_memory()
def service_add(self, service):
"""Add a service in this node."""
......@@ -47,7 +47,7 @@ class SimulatedNode:
"""Return the amount of free memory for this node"""
simulated_reservation = 0
for service in self.services: # type: Service
simulated_reservation += service.resource_reservation.memory
simulated_reservation += service.resource_reservation.memory.min
assert (self.real_free_resources['memory'] - simulated_reservation) >= 0
return self.real_free_resources['memory'] - simulated_reservation
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment