Skip to content

Commit e8a9fa1

Browse files
author
arttii
committed
some more progress
1 parent b1045a2 commit e8a9fa1

13 files changed

+196
-117
lines changed

mentor/__init__.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
import pkg_resources as _pkg_resources
44

55
from .scheduler import QueueScheduler, SchedulerDriver
6-
# from .executor import ThreadExecutor, ProcessExecutor, ExecutorDriver
7-
# #from .messages.satyr import PythonTask, PythonTaskStatus # important to register classes
8-
#
9-
#
10-
# __version__ = _pkg_resources.get_distribution('mentor').version
11-
#
12-
# __all__ = ('QueueScheduler',
13-
# 'SchedulerDriver',
14-
# 'ExecutorDriver',
15-
# 'ThreadExecutor',
16-
# 'ProcessExecutor',
17-
# 'PythonTask',
18-
# 'PythonTaskStatus')
6+
from .executor import ThreadExecutor, ProcessExecutor, ExecutorDriver
7+
from .messages import PythonTask, PythonTaskStatus
8+
9+
10+
__version__ = _pkg_resources.get_distribution('mentor').version
11+
12+
__all__ = ('QueueScheduler',
13+
'SchedulerDriver',
14+
'ExecutorDriver',
15+
'ThreadExecutor',
16+
'ProcessExecutor',
17+
'PythonTask',
18+
'PythonTaskStatus')

mentor/executor.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from mentos.executor import ExecutorDriver
1111
from mentos.interface import Executor
12-
#from mentor.messages.satyr import PythonTaskStatus, PythonTask
12+
from mentor.messages import PythonTaskStatus, PythonTask
1313
from mentor.utils import Interruptable
1414

1515
log = logging.getLogger(__name__)
@@ -45,7 +45,7 @@ def run(self, driver, task):
4545
driver.update(status)
4646
log.info('Sent TASK_FINISHED status update')
4747
finally:
48-
del self.tasks[task.task_id]
48+
del self.tasks[task.task_id.value]
4949
if self.is_idle(): # no more tasks left
5050
log.info('Executor stops due to no more executing '
5151
'tasks left')
@@ -57,7 +57,7 @@ def on_launch(self, driver, task):
5757
task = PythonTask(**task)
5858
thread = threading.Thread(target=self.run, args=(driver, task))
5959
# track tasks runned by this executor
60-
self.tasks[task.task_id] = thread
60+
self.tasks[task.task_id.value] = thread
6161
thread.start()
6262

6363
def on_kill(self, driver, task_id):
@@ -66,6 +66,11 @@ def on_kill(self, driver, task_id):
6666
def on_shutdown(self, driver):
6767
driver.stop()
6868

69+
def on_outbound_success(self, driver, request):
70+
pass
71+
72+
def on_outbound_error(self, driver, request, endpoint, error):
73+
pass
6974

7075
class ProcessExecutor(ThreadExecutor):
7176

@@ -88,6 +93,11 @@ def on_kill(self, driver, task_id):
8893
'tasks left')
8994
driver.stop()
9095

96+
def on_outbound_success(self, driver, event):
97+
pass
98+
99+
def on_outbound_error(self, driver, event):
100+
pass
91101

92102
if __name__ == '__main__':
93103
import logging

mentor/messages.py

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import cloudpickle
55
from .utils import remote_exception
66
import logging
7+
from uuid import uuid4
8+
from mentos.utils import decode_data,encode_data
79
log = logging.getLogger(__name__)
810

9-
11+
logging.getLogger().setLevel(logging.DEBUG)
1012
# u('string') replaces the forwards-incompatible u'string'
1113
if six.PY3:
1214
def u(string):
@@ -25,9 +27,24 @@ def u(string):
2527
iteritems = dict.iteritems
2628
iterkeys = dict.iterkeys
2729

30+
def bunchify(x):
31+
""" Recursively transforms a dictionary into a Message via copy.
32+
33+
"""
34+
35+
2836
class Message(dict):
2937
""" A dictionary that provides attribute-style access.
3038
"""
39+
@classmethod
40+
def convert(cls,x):
41+
if isinstance(x, dict):
42+
return cls((k, cls.convert(v)) for k, v in iteritems(x))
43+
elif isinstance(x, (list, tuple)):
44+
return type(x)(cls.convert(v) for v in x)
45+
else:
46+
return x
47+
3148

3249
def __contains__(self, k):
3350
"""
@@ -112,7 +129,10 @@ class ResourceMixin(object):
112129
@staticmethod
113130
def flatten(message):
114131
flattened = {}
115-
for r in message['resources']:
132+
if isinstance(message, (int, float, complex)):
133+
val = message
134+
message = {"resources":[Cpus(val),Disk(val),Mem(val)]}
135+
for r in message["resources"]:
116136
if r["type"]=="RANGES":
117137
flattened[r['name']] = r['ranges']['range']
118138
else:
@@ -190,21 +210,21 @@ def __isub__(self, second):
190210
def cpus(self):
191211
for res in self.resources:
192212
if res["name"] == "cpus":
193-
return res
213+
return Message.convert(res)
194214
return Cpus(0.0)
195215

196216
@property
197217
def mem(self):
198218
for res in self["resources"]:
199219
if res["name"] == "mem":
200-
return res
220+
return Message.convert(res)
201221
return Mem(0.0)
202222

203223
@property
204224
def disk(self):
205225
for res in self["resources"]:
206226
if res["name"] == "disk":
207-
return res
227+
return Message.convert(res)
208228
return Disk(0.0)
209229

210230
@property
@@ -218,28 +238,35 @@ def ports(self):
218238
class TaskInfo(ResourceMixin, Message):
219239
pass
220240

221-
class Offer(ResourceMixin, Message):
222-
pass
223-
224241

225242

226-
class PickleMixin(object):
243+
class Offer(ResourceMixin, Message):
244+
@property
245+
def slave_id(self):
246+
try:
247+
return self["slave_id"]
248+
except KeyError:
249+
return self["agent_id"]
227250

228251
@property
229-
def data(self):
230-
return cloudpickle.loads(self['data'])
252+
def agent_id(self):
253+
try:
254+
return self["agent_id"]
255+
except KeyError:
256+
return self["slave_id"]
231257

232-
@data.setter
233-
def data(self, value):
234-
self['data'] = cloudpickle.dumps(value)
235258

236259

237-
class PythonTaskStatus(PickleMixin, Message):
260+
class PythonTaskStatus(Message):
238261

239262
def __init__(self, data=None, **kwargs):
240263
super(PythonTaskStatus, self).__init__(**kwargs)
241264
self.labels = Message(labels=Message(key='python'))
242-
self.data = data
265+
self.data = encode_data(cloudpickle.dumps(data))
266+
267+
@property
268+
def result(self):
269+
return cloudpickle.loads(decode_data(self["data"]))
243270

244271
@property
245272
def exception(self):
@@ -248,41 +275,66 @@ def exception(self):
248275
except:
249276
return None
250277

278+
def is_staging(self):
279+
return self.state == 'TASK_STAGING'
280+
281+
def is_starting(self):
282+
return self.state == 'TASK_STARTING'
283+
284+
def is_running(self):
285+
return self.state == 'TASK_RUNNING'
286+
287+
def has_finished(self):
288+
return self.state == 'TASK_FINISHED'
289+
290+
def has_succeeded(self):
291+
return self.state == 'TASK_FINISHED'
292+
293+
def has_killed(self):
294+
return self.state == 'TASK_KILLED'
295+
296+
def has_failed(self):
297+
return self.state in ['TASK_FAILED', 'TASK_LOST', 'TASK_KILLED',
298+
'TASK_ERROR']
299+
300+
def has_terminated(self):
301+
return self.has_succeeded() or self.has_failed()
251302

252303
# TODO create custom messages per executor
253-
class PythonTask(PickleMixin, TaskInfo):
304+
class PythonTask(TaskInfo):
254305

255306

256307
def __init__(self, fn=None, args=[], kwargs={},
257308
resources=[Cpus(0.1), Mem(128), Disk(0)],
258309
executor=None, retries=3, **kwds):
259310
super(PythonTask, self).__init__(**kwds)
260-
self.status = PythonTaskStatus(task_id=self.id, state='TASK_STAGING')
261-
self.executor = executor or PythonExecutor()
262-
self.data = (fn, args, kwargs)
311+
self.task_id = self.get("task_id", Message(value=str(uuid4())))
312+
self.status = PythonTaskStatus(task_id=self.task_id, state='TASK_STAGING')
313+
self.executor = executor or PythonExecutor("python-executor")
314+
self.data = encode_data(cloudpickle.dumps(self.get("data",(fn, args, kwargs))))
263315
self.resources = resources
264316
self.retries = retries
265317
self.attempt = 1
266318

267319
self.labels = Message(labels=Message(key='python'))
268320

269321
def __call__(self):
270-
fn, args, kwargs = self.data
322+
fn, args, kwargs = cloudpickle.loads(decode_data(self.data))
271323
return fn(*args, **kwargs)
272324

273325
def retry(self, status):
274326
if self.attempt < self.retries:
275327
log.info('Task {} attempt #{} rescheduled due to failure with state '
276-
'{} and message {}'.format(self.id, self.attempt,
328+
'{} and message {}'.format(self.task_id, self.attempt,
277329
status.state, status.message))
278330
self.attempt += 1
279331
status.state = 'TASK_STAGING'
280332
else:
281333
log.error('Aborting due to task {} failed for {} attempts in state '
282-
'{} with message {}'.format(self.id, self.retries,
334+
'{} with message {}'.format(self.task_id, self.retries,
283335
status.state, status.message))
284336
raise RuntimeError('Task {} failed with state {} and message {}'.format(
285-
self.id, status.state, status.message))
337+
self.task_id, status.state, status.message))
286338

287339
def update(self, status):
288340
self.on_update(status)
@@ -294,14 +346,14 @@ def update(self, status):
294346
def on_update(self, status):
295347
self.status = status # update task's status
296348
log.info('Task {} has been updated with state {}'.format(
297-
self.id.value, status.state))
349+
self.task_id.value, status.state))
298350

299351
def on_success(self, status):
300-
log.info('Task {} has been succeded'.format(self.id.value))
352+
log.info('Task {} has been succeded'.format(self.task_id.value))
301353

302354
def on_fail(self, status):
303355
log.error('Task {} has been failed with state {} due to {}'.format(
304-
self.id.value, status.state, status.message))
356+
self.task_id.value, status.state, status.message))
305357

306358
try:
307359
raise status.exception # won't retry due to code error in PythonTaskStatus
@@ -310,21 +362,22 @@ def on_fail(self, status):
310362
self.retry(status)
311363
else:
312364
log.error('Aborting due to task {} failed with state {} and message '
313-
'{}'.format(self.id, status.state, status.message))
365+
'{}'.format(self.task_id, status.state, status.message))
314366

315367

316368
class PythonExecutor(Message):
317369

318370

319371

320-
def __init__(self, docker='satyr', force_pull=False,
372+
def __init__(self, id, docker='satyr', force_pull=False,
321373
envs={}, uris=[], **kwds):
322374
super(PythonExecutor, self).__init__(**kwds)
323375
self.container = Message(
324376
type='MESOS',
325377
mesos=Message(
326378
image=Message(type='DOCKER',
327379
docker=Message(name=docker))))
380+
self.executor_id = Message(value=id)
328381
self.command = Message(value='python -m satyr.executor',
329382
shell=True)
330383
self.force_pull = force_pull

mentor/placement.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ def weight(items, **kwargs):
88
raise ValueError('Missing attribute for weighting items!')
99
scaled = []
1010
for attr, weight in kwargs.items():
11-
values = [float(getattr(item, attr)) for item in items]
11+
try:
12+
values = [float(getattr(item, attr).scalar.value) for item in items]
13+
except:
14+
pass
1215
try:
1316
s = sum(values)
1417
scaled.append([weight * (v / s) for v in values])

0 commit comments

Comments
 (0)