Skip to content

Commit 57e03b9

Browse files
committed
FLEX: Support Interactive both in gsctl and coordinator side
1 parent f16da8c commit 57e03b9

File tree

20 files changed

+1167
-65
lines changed

20 files changed

+1167
-65
lines changed

coordinator/gscoordinator/coordinator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from graphscope.config import Config
3131
from graphscope.proto import coordinator_service_pb2_grpc
3232

33+
from gscoordinator.servicer import init_interactive_service_servicer
3334
from gscoordinator.servicer import init_graphscope_one_service_servicer
3435
from gscoordinator.utils import GS_GRPC_MAX_MESSAGE_LENGTH
3536

@@ -109,6 +110,7 @@ def get_servicer(config: Config):
109110
"""Get servicer of specified solution under FLEX architecture"""
110111
service_initializers = {
111112
"GraphScope One": init_graphscope_one_service_servicer,
113+
"Interactive": init_interactive_service_servicer,
112114
}
113115

114116
initializer = service_initializers.get(config.solution)
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#! /usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2023 Alibaba Group Holding Limited.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import datetime
20+
import json
21+
import time
22+
from abc import ABCMeta
23+
from abc import abstractmethod
24+
25+
import schedule
26+
from graphscope.framework.utils.py import random_string
27+
from schedule import CancelJob
28+
29+
from gscoordinator.stoppable_thread import StoppableThread
30+
from gscoordinator.utils import decode_datetimestr
31+
32+
33+
class Schedule(object):
34+
"""Schedule class that wrapper dbader schedule
35+
36+
Repo: https://github.com/dbader/schedule.
37+
"""
38+
39+
def __init__(self):
40+
self._schedule = schedule.Scheduler()
41+
self._run_pending_thread = StoppableThread(target=self.run_pending, args=())
42+
self._run_pending_thread.daemon = True
43+
self._run_pending_thread.start()
44+
45+
@property
46+
def schedule(self):
47+
return self._schedule
48+
49+
def run_pending(self):
50+
"""Run all jobs that are scheduled to run."""
51+
while True:
52+
self._schedule.run_pending()
53+
time.sleep(1)
54+
55+
56+
schedule = Schedule().schedule # noqa: F811
57+
58+
59+
class Scheduler(metaclass=ABCMeta):
60+
"""
61+
Objects instantiated by the :class:`Scheduler <Scheduler>` are
62+
factories to create jobs, keep record of scheduled jobs and
63+
handle their execution in the :method:`run` method.
64+
"""
65+
66+
def __init__(self, at_time, repeat):
67+
# scheduler id
68+
self._scheduler_id = "Job-{0}".format(random_string(16)).upper()
69+
# periodic job as used
70+
self._job = None
71+
# true will be run immediately
72+
self._run_now = False
73+
# time at which this job to schedule
74+
self._at_time = self._decode_datetimestr(at_time)
75+
# repeat every day or week, or run job once(no repeat)
76+
# optional value "day", "week", "null"
77+
self._repeat = repeat
78+
# job running thread, note that:
79+
# the last job should be end of execution at the beginning of the next job
80+
self._running_thread = None
81+
# tags
82+
self._tags = []
83+
84+
# when the job actually scheduled, the following variables will be generated and overridden.
85+
self._jobid = None
86+
self._last_run = None
87+
88+
def _decode_datetimestr(self, datetime_str):
89+
if datetime_str == "now":
90+
self._run_now = True
91+
return datetime.datetime.now()
92+
return decode_datetimestr(datetime_str)
93+
94+
def __str__(self):
95+
return "Scheduler(at_time={}, repeat={})".format(self._at_time, self._repeat)
96+
97+
@property
98+
def monday(self):
99+
return self._at_time.weekday() == 0
100+
101+
@property
102+
def tuesday(self):
103+
return self._at_time.weekday() == 1
104+
105+
@property
106+
def wednesday(self):
107+
return self._at_time.weekday() == 2
108+
109+
@property
110+
def thursday(self):
111+
return self._at_time.weekday() == 3
112+
113+
@property
114+
def friday(self):
115+
return self._at_time.weekday() == 4
116+
117+
@property
118+
def saturday(self):
119+
return self._at_time.weekday() == 5
120+
121+
@property
122+
def sunday(self):
123+
return self._at_time.weekday() == 6
124+
125+
@property
126+
def timestr(self):
127+
"""return str of the time object.
128+
time([hour[, minute[, second[, microsecond[, tzinfo]]]]]) --> a time object
129+
"""
130+
return str(self._at_time.time())
131+
132+
@property
133+
def job(self):
134+
"""A periodic job managed by the dbader scheduler.
135+
https://github.com/dbader/schedule.
136+
"""
137+
return self._job
138+
139+
@property
140+
def jobid(self):
141+
"""id for the last scheduled job"""
142+
return self._jobid
143+
144+
@property
145+
def schedulerid(self):
146+
"""id for the scheduler"""
147+
return self._scheduler_id
148+
149+
@property
150+
def last_run(self):
151+
"""datetime of the last run"""
152+
return self._last_run
153+
154+
@property
155+
def tags(self):
156+
return self._tags
157+
158+
@property
159+
def running_thread(self):
160+
return self._running_thread
161+
162+
def run_once(self):
163+
"""Run the job immediately."""
164+
self.do_run()
165+
return CancelJob
166+
167+
def waiting_until_to_run(self):
168+
"""Run the job once at a specific time."""
169+
if datetime.datetime.now() >= self._at_time:
170+
return self.run_once()
171+
172+
def do_run(self):
173+
"""Start a thread for the job."""
174+
# overwrite for each scheduled job
175+
self._jobid = "job-{0}".format(random_string(16)).upper()
176+
self._last_run = datetime.datetime.now()
177+
# schedule in a thread
178+
self._running_thread = StoppableThread(target=self.run, args=())
179+
self._running_thread.daemon = True
180+
self._running_thread.start()
181+
182+
def submit(self):
183+
if not self._run_now and self._repeat not in ["week", "day", "null", None]:
184+
raise RuntimeError(
185+
"Submit schedule job failed: at_time is '{0}', repeat is '{1}'".format(
186+
self._at_time, self._repeat
187+
)
188+
)
189+
190+
if self._run_now:
191+
self._job = schedule.every().seconds.do(self.run_once)
192+
193+
if not self._run_now and self._repeat == "week":
194+
if self.monday:
195+
self._job = schedule.every().monday.at(self.timestr).do(self.do_run)
196+
elif self.tuesday:
197+
self._job = schedule.every().tuesday.at(self.timestr).do(self.do_run)
198+
elif self.wednesday:
199+
self._job = schedule.every().wednesday.at(self.timestr).do(self.do_run)
200+
elif self.thursday:
201+
self._job = schedule.every().thursday.at(self.timestr).do(self.do_run)
202+
elif self.friday:
203+
self._job = schedule.every().friday.at(self.timestr).do(self.do_run)
204+
elif self.saturday:
205+
self._job = schedule.every().saturday.at(self.timestr).do(self.do_run)
206+
elif self.sunday:
207+
self._job = schedule.every().sunday.at(self.timestr).do(self.do_run)
208+
209+
if not self._run_now and self._repeat == "day":
210+
self._job = schedule.every().day.at(self.timestr).do(self.do_run)
211+
212+
if not self._run_now and self._repeat in ["null", None]:
213+
self._job = (
214+
schedule.every().day.at(self.timestr).do(self.waiting_until_to_run)
215+
)
216+
217+
# tag
218+
self._job.tag(self._scheduler_id, *self._tags)
219+
220+
def cancel(self):
221+
"""
222+
Set the running job thread stoppable and wait for the
223+
thread to exit properly by using join() method.
224+
"""
225+
if self._running_thread is not None and self._running_thread.is_alive():
226+
self._running_thread.stop()
227+
self._running_thread.join()
228+
229+
@abstractmethod
230+
def run(self):
231+
"""
232+
Methods that all subclasses need to implement, note that
233+
subclass needs to handle exception by itself.
234+
"""
235+
raise NotImplementedError
236+
237+
238+
def cancel_job(job, delete_scheduler=True):
239+
"""
240+
Cancel the job which going to scheduled or cancel the whole scheduler.
241+
242+
Args:
243+
job: Periodic job as used by :class:`Scheduler`.
244+
delete_scheduler: True will can the whole scheduler, otherwise,
245+
delay the next-run time by on period.
246+
"""
247+
if delete_scheduler:
248+
schedule.cancel_job(job)
249+
else:
250+
job.next_run += job.period

coordinator/gscoordinator/servicer/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
# limitations under the License.
1717
#
1818

19+
from gscoordinator.servicer.interactive.service import *
1920
from gscoordinator.servicer.graphscope_one.service import *
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#! /usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2023 Alibaba Group Holding Limited.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import atexit
20+
import logging
21+
22+
from graphscope.config import Config
23+
from graphscope.proto import coordinator_service_pb2_grpc
24+
from graphscope.proto import message_pb2
25+
26+
27+
class BaseServiceServicer(coordinator_service_pb2_grpc.CoordinatorServiceServicer):
28+
"""Base class of coordinator service"""
29+
30+
def __init__(self, config: Config):
31+
self._config = config
32+
atexit.register(self.cleanup)
33+
34+
def __del__(self):
35+
self.cleanup()
36+
37+
def Connect(self, request, context):
38+
return message_pb2.ConnectResponse(solution=self._config.solution)
39+
40+
@property
41+
def launcher_type(self):
42+
return self._config.launcher_type
43+
44+
def cleanup(self):
45+
pass
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#! /usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2023 Alibaba Group Holding Limited.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import os
20+
import sys
21+
22+
try:
23+
sys.path.insert(0, os.path.dirname(__file__))
24+
import interactive_client
25+
except ImportError:
26+
raise

0 commit comments

Comments
 (0)