jaseci 1.4.0.9__py3-none-any.whl → 1.4.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaseci might be problematic. Click here for more details.
- jaseci/VERSION +1 -1
- jaseci/__init__.py +3 -0
- jaseci/actions/standard/elastic.py +3 -2
- jaseci/actions/standard/mail.py +3 -2
- jaseci/actions/standard/std.py +3 -2
- jaseci/actions/standard/stripe.py +3 -2
- jaseci/actions/standard/task.py +3 -5
- jaseci/actions/standard/tests/test_mail_lib.py +8 -7
- jaseci/actions/tests/test_std.py +4 -5
- jaseci/actor/walker.py +6 -3
- jaseci/api/config_api.py +3 -2
- jaseci/api/jac_api.py +2 -2
- jaseci/api/jsorc_api.py +60 -121
- jaseci/api/prometheus_api.py +14 -20
- jaseci/api/queue_api.py +9 -5
- jaseci/api/tests/test_global_api.py +3 -3
- jaseci/api/tests/test_logger_api.py +3 -3
- jaseci/api/user_api.py +3 -3
- jaseci/api/webhook_api.py +6 -4
- jaseci/attr/action.py +10 -4
- jaseci/element/master.py +2 -0
- jaseci/element/super_master.py +2 -0
- jaseci/hook/memory.py +3 -1
- jaseci/hook/redis.py +5 -4
- jaseci/jac/interpreter/interp.py +16 -4
- jaseci/jac/tests/test_book.py +2 -2
- jaseci/jsctl/jsctl.py +48 -15
- jaseci/jsctl/tests/test_jsctl.py +5 -0
- jaseci/jsorc.py +733 -0
- jaseci/jsorc_settings.py +184 -0
- jaseci/manifests/database.yaml +107 -0
- jaseci/manifests/elastic.yaml +5923 -0
- jaseci/manifests/prometheus.yaml +1273 -0
- jaseci/{svc/jsorc-backup/jaseci-redis.yaml → manifests/redis.yaml} +20 -0
- jaseci/svc/__init__.py +0 -25
- jaseci/svc/{elastic/elastic.py → elastic_svc.py} +5 -16
- jaseci/svc/kube_svc.py +240 -0
- jaseci/svc/{mail/mail.py → mail_svc.py} +14 -17
- jaseci/svc/{prometheus/prometheus.py → prome_svc.py} +5 -16
- jaseci/svc/{redis/redis.py → redis_svc.py} +14 -26
- jaseci/svc/{stripe/stripe.py → stripe_svc.py} +4 -7
- jaseci/svc/{task/task.py → task_svc.py} +27 -24
- jaseci/svc/{task/common.py → tasks.py} +287 -293
- jaseci/tests/jac_test_progs.py +21 -0
- jaseci/tests/test_core.py +14 -15
- jaseci/tests/test_jac.py +59 -60
- jaseci/tests/test_node.py +6 -13
- jaseci/tests/test_progs.py +74 -52
- jaseci/tests/test_stripe.py +6 -10
- jaseci/utils/actions/actions_manager.py +254 -0
- jaseci/{svc/actions_optimizer → utils/actions}/actions_optimizer.py +9 -19
- jaseci/utils/json_handler.py +2 -3
- jaseci/utils/test_core.py +4 -5
- jaseci/utils/utils.py +12 -0
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/METADATA +2 -1
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/RECORD +63 -80
- jaseci/svc/common.py +0 -763
- jaseci/svc/config.py +0 -9
- jaseci/svc/elastic/__init__.py +0 -3
- jaseci/svc/elastic/config.py +0 -8
- jaseci/svc/elastic/manifest.py +0 -1
- jaseci/svc/jsorc-backup/jsorc.py +0 -182
- jaseci/svc/jsorc-backup/promon/__init__.py +0 -0
- jaseci/svc/jsorc-backup/promon/promon.py +0 -202
- jaseci/svc/mail/__init__.py +0 -4
- jaseci/svc/mail/config.py +0 -25
- jaseci/svc/meta.py +0 -164
- jaseci/svc/postgres/__init__.py +0 -0
- jaseci/svc/postgres/manifest.py +0 -106
- jaseci/svc/prometheus/__init__.py +0 -5
- jaseci/svc/prometheus/config.py +0 -11
- jaseci/svc/prometheus/manifest.py +0 -1102
- jaseci/svc/redis/__init__.py +0 -5
- jaseci/svc/redis/config.py +0 -10
- jaseci/svc/redis/manifest.py +0 -65
- jaseci/svc/state.py +0 -17
- jaseci/svc/stripe/__init__.py +0 -3
- jaseci/svc/stripe/config.py +0 -7
- jaseci/svc/task/__init__.py +0 -5
- jaseci/svc/task/config.py +0 -17
- /jaseci/{svc/actions_optimizer → manifests}/__init__.py +0 -0
- /jaseci/{svc/jsorc-backup → utils/actions}/__init__.py +0 -0
- /jaseci/{svc/actions_optimizer → utils/actions}/actions_state.py +0 -0
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/LICENSE +0 -0
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/WHEEL +0 -0
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/entry_points.txt +0 -0
- {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/top_level.txt +0 -0
jaseci/jsorc.py
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import signal
|
|
2
|
+
import psycopg2
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from time import sleep
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import TypeVar, Any, Union
|
|
9
|
+
|
|
10
|
+
from .utils.utils import logger
|
|
11
|
+
from .jsorc_settings import JsOrcSettings
|
|
12
|
+
|
|
13
|
+
from kubernetes.client.rest import ApiException
|
|
14
|
+
from multiprocessing import Process, current_process
|
|
15
|
+
|
|
16
|
+
# For future use
|
|
17
|
+
# from concurrent.futures import ThreadPoolExecutor
|
|
18
|
+
# from os import cpu_count
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class State(Enum):
|
|
24
|
+
FAILED = -1
|
|
25
|
+
NOT_STARTED = 0
|
|
26
|
+
STARTED = 1
|
|
27
|
+
RUNNING = 2
|
|
28
|
+
|
|
29
|
+
def is_ready(self):
|
|
30
|
+
return self == State.NOT_STARTED
|
|
31
|
+
|
|
32
|
+
def is_running(self):
|
|
33
|
+
return self == State.RUNNING
|
|
34
|
+
|
|
35
|
+
def has_failed(self):
|
|
36
|
+
return self == State.FAILED
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class JsOrc:
|
|
40
|
+
#######################################################################################################
|
|
41
|
+
# INNER CLASS #
|
|
42
|
+
#######################################################################################################
|
|
43
|
+
|
|
44
|
+
class CommonService:
|
|
45
|
+
###################################################
|
|
46
|
+
# PROPERTIES #
|
|
47
|
+
###################################################
|
|
48
|
+
|
|
49
|
+
# ------------------- DAEMON -------------------- #
|
|
50
|
+
|
|
51
|
+
_daemon = {}
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def daemon(self):
|
|
55
|
+
return __class__._daemon
|
|
56
|
+
|
|
57
|
+
def __init__(self, config: dict, manifest: dict):
|
|
58
|
+
self.app = None
|
|
59
|
+
self.error = None
|
|
60
|
+
self.state = State.NOT_STARTED
|
|
61
|
+
|
|
62
|
+
# ------------------- CONFIG -------------------- #
|
|
63
|
+
|
|
64
|
+
self.config = config
|
|
65
|
+
self.enabled = config.pop("enabled", False)
|
|
66
|
+
self.quiet = config.pop("quiet", False)
|
|
67
|
+
self.automated = config.pop("automated", False)
|
|
68
|
+
|
|
69
|
+
# ------------------ MANIFEST ------------------- #
|
|
70
|
+
|
|
71
|
+
self.manifest = manifest
|
|
72
|
+
self.manifest_meta = {
|
|
73
|
+
"__OLD_CONFIG__": manifest.pop("__OLD_CONFIG__", {}),
|
|
74
|
+
"__UNSAFE_PARAPHRASE__": manifest.pop("__UNSAFE_PARAPHRASE__", ""),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
self.start()
|
|
78
|
+
|
|
79
|
+
###################################################
|
|
80
|
+
# BUILDER #
|
|
81
|
+
###################################################
|
|
82
|
+
|
|
83
|
+
def start(self):
|
|
84
|
+
try:
|
|
85
|
+
if self.enabled and self.is_ready():
|
|
86
|
+
self.state = State.STARTED
|
|
87
|
+
self.run()
|
|
88
|
+
self.state = State.RUNNING
|
|
89
|
+
self.post_run()
|
|
90
|
+
except Exception as e:
|
|
91
|
+
if not (self.quiet):
|
|
92
|
+
logger.error(
|
|
93
|
+
f"Skipping {self.__class__.__name__} due to initialization "
|
|
94
|
+
f"failure!\n{e.__class__.__name__}: {e}"
|
|
95
|
+
)
|
|
96
|
+
self.failed(e)
|
|
97
|
+
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def run(self):
|
|
101
|
+
raise Exception(f"Not properly configured! Please override run method!")
|
|
102
|
+
|
|
103
|
+
def post_run(self):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# ------------------- DAEMON -------------------- #
|
|
107
|
+
|
|
108
|
+
def spawn_daemon(self, **targets):
|
|
109
|
+
if current_process().name == "MainProcess":
|
|
110
|
+
for name, target in targets.items():
|
|
111
|
+
dae: Process = self.daemon.get(name)
|
|
112
|
+
if not dae or not dae.is_alive():
|
|
113
|
+
process = Process(target=target, daemon=True)
|
|
114
|
+
process.start()
|
|
115
|
+
self.daemon[name] = process
|
|
116
|
+
|
|
117
|
+
def terminate_daemon(self, *names):
|
|
118
|
+
for name in names:
|
|
119
|
+
dae: Process = self.daemon.pop(name, None)
|
|
120
|
+
if not (dae is None) and dae.is_alive():
|
|
121
|
+
logger.info(f"Terminating {name} ...")
|
|
122
|
+
dae.terminate()
|
|
123
|
+
|
|
124
|
+
###################################################
|
|
125
|
+
# COMMONS #
|
|
126
|
+
###################################################
|
|
127
|
+
|
|
128
|
+
def poke(self, cast: T = None, msg: str = None) -> Union[T, Any]:
|
|
129
|
+
if self.is_running():
|
|
130
|
+
return (
|
|
131
|
+
self
|
|
132
|
+
if cast and cast.__name__ == self.__class__.__name__
|
|
133
|
+
else self.app
|
|
134
|
+
)
|
|
135
|
+
raise Exception(
|
|
136
|
+
msg or f"{self.__class__.__name__} is disabled or not yet configured!"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def is_ready(self):
|
|
140
|
+
return self.state.is_ready() and self.app is None
|
|
141
|
+
|
|
142
|
+
def is_running(self):
|
|
143
|
+
return self.state.is_running() and not (self.app is None)
|
|
144
|
+
|
|
145
|
+
def has_failed(self):
|
|
146
|
+
return self.state.has_failed()
|
|
147
|
+
|
|
148
|
+
def failed(self, error: Exception = None):
|
|
149
|
+
self.app = None
|
|
150
|
+
self.state = State.FAILED
|
|
151
|
+
self.error = error
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def proxy(cls):
|
|
155
|
+
return cls({}, {})
|
|
156
|
+
|
|
157
|
+
# ---------------- PROXY EVENTS ----------------- #
|
|
158
|
+
|
|
159
|
+
def on_delete(self):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
# ------------------- EVENTS -------------------- #
|
|
163
|
+
|
|
164
|
+
def __del__(self):
|
|
165
|
+
self.on_delete()
|
|
166
|
+
|
|
167
|
+
def __getstate__(self):
|
|
168
|
+
return {}
|
|
169
|
+
|
|
170
|
+
def __setstate__(self, ignored):
|
|
171
|
+
# for build on pickle load
|
|
172
|
+
self.state = State.FAILED
|
|
173
|
+
del self
|
|
174
|
+
|
|
175
|
+
#######################################################################################################
|
|
176
|
+
# JSORC CLASS #
|
|
177
|
+
#######################################################################################################
|
|
178
|
+
|
|
179
|
+
# ----------------- REFERENCE ----------------- #
|
|
180
|
+
|
|
181
|
+
_contexts = {}
|
|
182
|
+
_context_instances = {}
|
|
183
|
+
|
|
184
|
+
_services = {}
|
|
185
|
+
_service_instances = {}
|
|
186
|
+
|
|
187
|
+
_repositories = {}
|
|
188
|
+
|
|
189
|
+
# ----------------- SETTINGS ----------------- #
|
|
190
|
+
|
|
191
|
+
_use_proxy = False
|
|
192
|
+
_settings = JsOrcSettings
|
|
193
|
+
|
|
194
|
+
# For future use
|
|
195
|
+
# _executor = ThreadPoolExecutor(
|
|
196
|
+
# min(4, int((cpu_count() or 2) / 4) + 1)
|
|
197
|
+
# )
|
|
198
|
+
|
|
199
|
+
# ------------------ COMMONS ------------------ #
|
|
200
|
+
|
|
201
|
+
_backoff_interval = 10
|
|
202
|
+
_running_interval = 0
|
|
203
|
+
__running__ = False
|
|
204
|
+
__proxy__ = CommonService.proxy()
|
|
205
|
+
|
|
206
|
+
# ------------------- REGEN ------------------- #
|
|
207
|
+
|
|
208
|
+
_regeneration_queues = []
|
|
209
|
+
_regenerating = False
|
|
210
|
+
_has_db = False
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def push(name: str, target: dict, entry: dict):
|
|
214
|
+
if name not in target:
|
|
215
|
+
target[name] = [entry]
|
|
216
|
+
else:
|
|
217
|
+
target[name] = sorted(
|
|
218
|
+
target[name] + [entry],
|
|
219
|
+
key=lambda item: (-item["priority"], -item["date_added"]),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def run(cls):
|
|
224
|
+
if not cls.__running__:
|
|
225
|
+
cls.__running__ == True
|
|
226
|
+
config = cls.settings("JSORC_CONFIG")
|
|
227
|
+
if cls.db_check():
|
|
228
|
+
hook = cls.hook()
|
|
229
|
+
config = hook.service_glob("JSORC_CONFIG", config)
|
|
230
|
+
cls._backoff_interval = max(5, config.get("backoff_interval", 10))
|
|
231
|
+
cls._regeneration_queues = config.get("pre_loaded_services", [])
|
|
232
|
+
cls.push_interval(1)
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def push_interval(cls, interval):
|
|
236
|
+
if cls._running_interval == 0:
|
|
237
|
+
cls._running_interval += 1
|
|
238
|
+
signal.alarm(interval)
|
|
239
|
+
else:
|
|
240
|
+
logger.info("Reusing current running interval...")
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def kube(cls):
|
|
244
|
+
if not cls._kube:
|
|
245
|
+
raise Exception(f"Kubernetes is not yet ready!")
|
|
246
|
+
return cls._kube
|
|
247
|
+
|
|
248
|
+
#################################################
|
|
249
|
+
# HELPER #
|
|
250
|
+
#################################################
|
|
251
|
+
|
|
252
|
+
# ------------------ context ------------------ #
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def get(cls, context: str, cast: T = None, *args, **kwargs) -> Union[T, Any]:
|
|
256
|
+
"""
|
|
257
|
+
Get existing context instance or build a new one that will persists
|
|
258
|
+
ex: master
|
|
259
|
+
|
|
260
|
+
context: name of the context to be build
|
|
261
|
+
cast: to cast the return and allow code hinting
|
|
262
|
+
*arsgs: additional argument used to initialize context
|
|
263
|
+
**kwargs: additional keyword argument used to initialize context
|
|
264
|
+
"""
|
|
265
|
+
if context not in cls._contexts:
|
|
266
|
+
raise Exception(f"Context {context} is not existing!")
|
|
267
|
+
|
|
268
|
+
if context not in cls._context_instances:
|
|
269
|
+
cls._context_instances[context] = cls.ctx(context, cast, *args, **kwargs)
|
|
270
|
+
|
|
271
|
+
return cls._context_instances[context]
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def destroy(cls, context: str):
|
|
275
|
+
"""
|
|
276
|
+
remove existing context instance
|
|
277
|
+
ex: master
|
|
278
|
+
|
|
279
|
+
context: name of the context to be build
|
|
280
|
+
"""
|
|
281
|
+
if context not in cls._contexts:
|
|
282
|
+
raise Exception(f"Context {context} is not existing!")
|
|
283
|
+
|
|
284
|
+
if context in cls._context_instances:
|
|
285
|
+
del cls._context_instances[context]
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def renew(cls, context: str, cast: T = None, *args, **kwargs) -> Union[T, Any]:
|
|
289
|
+
"""
|
|
290
|
+
renew existing context instance or build a new one that will persists
|
|
291
|
+
ex: master
|
|
292
|
+
|
|
293
|
+
context: name of the context to be build
|
|
294
|
+
cast: to cast the return and allow code hinting
|
|
295
|
+
*arsgs: additional argument used to initialize context
|
|
296
|
+
**kwargs: additional keyword argument used to initialize context
|
|
297
|
+
"""
|
|
298
|
+
cls.destroy(context)
|
|
299
|
+
|
|
300
|
+
cls._context_instances[context] = cls.ctx(context, cast, *args, **kwargs)
|
|
301
|
+
return cls._context_instances[context]
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def ctx(cls, context: str, cast: T = None, *args, **kwargs) -> Union[T, Any]:
|
|
305
|
+
"""
|
|
306
|
+
Build new instance of the context
|
|
307
|
+
ex: master
|
|
308
|
+
|
|
309
|
+
context: name of the context to be build
|
|
310
|
+
cast: to cast the return and allow code hinting
|
|
311
|
+
*arsgs: additional argument used to initialize context
|
|
312
|
+
**kwargs: additional keyword argument used to initialize context
|
|
313
|
+
"""
|
|
314
|
+
if context not in cls._contexts:
|
|
315
|
+
raise Exception(f"Context {context} is not existing!")
|
|
316
|
+
|
|
317
|
+
# highest priority
|
|
318
|
+
context = cls._contexts[context][0]
|
|
319
|
+
|
|
320
|
+
return context["type"](*args, **kwargs)
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def ctx_cls(cls, context: str):
|
|
324
|
+
"""
|
|
325
|
+
Get the context class
|
|
326
|
+
"""
|
|
327
|
+
if context not in cls._contexts:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
return cls._contexts[context][0]["type"]
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def master(cls, cast: T = None, *args, **kwargs) -> Union[T, Any]:
|
|
334
|
+
"""
|
|
335
|
+
Generate master instance
|
|
336
|
+
"""
|
|
337
|
+
return cls.__gen_with_hook("master", cast, *args, **kwargs)
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def super_master(cls, cast: T = None, *args, **kwargs) -> Union[T, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Generate super_master instance
|
|
343
|
+
"""
|
|
344
|
+
return cls.__gen_with_hook("super_master", cast, *args, **kwargs)
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def __gen_with_hook(cls, context: str, *args, **kwargs):
|
|
348
|
+
"""
|
|
349
|
+
Common process on master and super_master
|
|
350
|
+
"""
|
|
351
|
+
if not kwargs.get("h", None):
|
|
352
|
+
kwargs["h"] = cls.hook()
|
|
353
|
+
|
|
354
|
+
return cls.ctx(context, None, *args, **kwargs)
|
|
355
|
+
|
|
356
|
+
# ------------------ service ------------------ #
|
|
357
|
+
|
|
358
|
+
@classmethod
|
|
359
|
+
def _svc(cls, service: str) -> CommonService:
|
|
360
|
+
if service not in cls._services:
|
|
361
|
+
raise Exception(f"Service {service} is not existing!")
|
|
362
|
+
|
|
363
|
+
# highest priority
|
|
364
|
+
instance = cls._services[service][0]
|
|
365
|
+
|
|
366
|
+
config = cls.settings(instance["config"], cls.settings("DEFAULT_CONFIG"))
|
|
367
|
+
manifest = cls.settings(
|
|
368
|
+
instance["manifest"] or "DEFAULT_MANIFEST", cls.settings("DEFAULT_MANIFEST")
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if cls.db_check():
|
|
372
|
+
hook = cls.hook(use_proxy=instance["proxy"])
|
|
373
|
+
|
|
374
|
+
config = hook.service_glob(instance["config"], config)
|
|
375
|
+
|
|
376
|
+
manifest = (
|
|
377
|
+
hook.service_glob(instance["manifest"], manifest)
|
|
378
|
+
if instance["manifest"]
|
|
379
|
+
else {}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
instance: JsOrc.CommonService = instance["type"](config, manifest)
|
|
383
|
+
|
|
384
|
+
if instance.has_failed() and service not in cls._regeneration_queues:
|
|
385
|
+
cls._regeneration_queues.append(service)
|
|
386
|
+
|
|
387
|
+
return instance
|
|
388
|
+
|
|
389
|
+
@classmethod
|
|
390
|
+
def svc(cls, service: str, cast: T = None) -> Union[T, CommonService]:
|
|
391
|
+
"""
|
|
392
|
+
Get service. Initialize when not yet existing.
|
|
393
|
+
ex: task
|
|
394
|
+
|
|
395
|
+
service: name of the service to be reference
|
|
396
|
+
cast: to cast the return and allow code hinting
|
|
397
|
+
"""
|
|
398
|
+
if service not in cls._services:
|
|
399
|
+
raise Exception(f"Service {service} is not existing!")
|
|
400
|
+
|
|
401
|
+
if service not in cls._service_instances:
|
|
402
|
+
if cls._use_proxy and cls._services[service][0]["proxy"]:
|
|
403
|
+
return cls.__proxy__
|
|
404
|
+
cls._service_instances[service] = cls._svc(service)
|
|
405
|
+
|
|
406
|
+
return cls._service_instances[service]
|
|
407
|
+
|
|
408
|
+
@classmethod
|
|
409
|
+
def svc_reset(cls, service, cast: T = None) -> Union[T, CommonService]:
|
|
410
|
+
"""
|
|
411
|
+
Service reset now deletes the actual instance and rebuild it
|
|
412
|
+
"""
|
|
413
|
+
if service in cls._service_instances:
|
|
414
|
+
instance = cls._service_instances.pop(service)
|
|
415
|
+
del instance
|
|
416
|
+
return cls.svc(service)
|
|
417
|
+
|
|
418
|
+
# ---------------- repository ----------------- #
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def src(cls, repository: str, cast: T = None) -> Union[T, Any]:
|
|
422
|
+
"""
|
|
423
|
+
Initialize datasource class (repository)
|
|
424
|
+
ex: hook
|
|
425
|
+
|
|
426
|
+
repository: name of the repository to be reference
|
|
427
|
+
cast: to cast the return and allow code hinting
|
|
428
|
+
"""
|
|
429
|
+
if repository not in cls._repositories:
|
|
430
|
+
raise Exception(f"Repository {repository} is not existing!")
|
|
431
|
+
|
|
432
|
+
# highest priority
|
|
433
|
+
repository = cls._repositories[repository][0]
|
|
434
|
+
|
|
435
|
+
return repository["type"]()
|
|
436
|
+
|
|
437
|
+
@classmethod
|
|
438
|
+
def hook(cls, cast: T = None, use_proxy: bool = False) -> Union[T, Any]:
|
|
439
|
+
"""
|
|
440
|
+
Generate hook repository instance
|
|
441
|
+
"""
|
|
442
|
+
cls._use_proxy = use_proxy
|
|
443
|
+
hook = cls.src("hook")
|
|
444
|
+
cls._use_proxy = False
|
|
445
|
+
return hook
|
|
446
|
+
|
|
447
|
+
#################################################
|
|
448
|
+
# DECORATORS #
|
|
449
|
+
#################################################
|
|
450
|
+
|
|
451
|
+
@classmethod
|
|
452
|
+
def service(
|
|
453
|
+
cls,
|
|
454
|
+
name: str = None,
|
|
455
|
+
config: str = None,
|
|
456
|
+
manifest: str = None,
|
|
457
|
+
priority: int = 0,
|
|
458
|
+
proxy: bool = False,
|
|
459
|
+
):
|
|
460
|
+
"""
|
|
461
|
+
Save the class in services options
|
|
462
|
+
name: name to be used for reference
|
|
463
|
+
config: config name from datasource
|
|
464
|
+
manifest: manifest name from datasource
|
|
465
|
+
priority: duplicate name will use the highest priority
|
|
466
|
+
proxy: allow proxy service
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
def decorator(service: T) -> T:
|
|
470
|
+
cls.push(
|
|
471
|
+
name=name or service.__name__,
|
|
472
|
+
target=cls._services,
|
|
473
|
+
entry={
|
|
474
|
+
"type": service,
|
|
475
|
+
"config": config or f"{name.upper()}_CONFIG",
|
|
476
|
+
"manifest": manifest,
|
|
477
|
+
"priority": priority,
|
|
478
|
+
"proxy": proxy,
|
|
479
|
+
"date_added": int(datetime.utcnow().timestamp() * 1000),
|
|
480
|
+
},
|
|
481
|
+
)
|
|
482
|
+
return service
|
|
483
|
+
|
|
484
|
+
return decorator
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
def repository(cls, name: str = None, priority: int = 0):
|
|
488
|
+
"""
|
|
489
|
+
Save the class in repositories options
|
|
490
|
+
name: name to be used for reference
|
|
491
|
+
priority: duplicate name will use the highest priority
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
def decorator(repository: T) -> T:
|
|
495
|
+
cls.push(
|
|
496
|
+
name=name or repository.__name__,
|
|
497
|
+
target=cls._repositories,
|
|
498
|
+
entry={
|
|
499
|
+
"type": repository,
|
|
500
|
+
"priority": priority,
|
|
501
|
+
"date_added": int(datetime.utcnow().timestamp() * 1000),
|
|
502
|
+
},
|
|
503
|
+
)
|
|
504
|
+
return repository
|
|
505
|
+
|
|
506
|
+
return decorator
|
|
507
|
+
|
|
508
|
+
@classmethod
|
|
509
|
+
def context(cls, name: str = None, priority: int = 0):
|
|
510
|
+
"""
|
|
511
|
+
Save the class in contexts options
|
|
512
|
+
name: name to be used for reference
|
|
513
|
+
priority: duplicate name will use the highest priority
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
def decorator(context: T) -> T:
|
|
517
|
+
cls.push(
|
|
518
|
+
name=name or context.__name__,
|
|
519
|
+
target=cls._contexts,
|
|
520
|
+
entry={
|
|
521
|
+
"type": context,
|
|
522
|
+
"priority": priority,
|
|
523
|
+
"date_added": int(datetime.utcnow().timestamp() * 1000),
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
return context
|
|
527
|
+
|
|
528
|
+
return decorator
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def inject(cls, contexts: list = [], services: list = [], repositories: list = []):
|
|
532
|
+
"""
|
|
533
|
+
Allow to inject instance on specific method/class
|
|
534
|
+
contexts: list of context name to inject
|
|
535
|
+
- can use tuple per entry (name, alias) instead of string
|
|
536
|
+
services: list of service name to inject
|
|
537
|
+
- can use tuple per entry (name, alias) instead of string
|
|
538
|
+
repositories: list of service name to inject
|
|
539
|
+
- can use tuple per entry (name, alias) instead of string
|
|
540
|
+
"""
|
|
541
|
+
|
|
542
|
+
def decorator(callable):
|
|
543
|
+
def argument_handler(*args, **kwargs):
|
|
544
|
+
_instances = {}
|
|
545
|
+
|
|
546
|
+
for context in contexts:
|
|
547
|
+
if isinstance(context, tuple):
|
|
548
|
+
_instances[context[1]] = cls.ctx(context[0])
|
|
549
|
+
else:
|
|
550
|
+
_instances[context] = cls.ctx(context)
|
|
551
|
+
for repository in repositories:
|
|
552
|
+
if isinstance(repository, tuple):
|
|
553
|
+
_instances[repository[1]] = cls.src(repository[0])
|
|
554
|
+
else:
|
|
555
|
+
_instances[repository] = cls.src(repository)
|
|
556
|
+
for service in services:
|
|
557
|
+
if isinstance(service, tuple):
|
|
558
|
+
_instances[service[1]] = cls.svc(service[0])
|
|
559
|
+
else:
|
|
560
|
+
_instances[service] = cls.svc(service)
|
|
561
|
+
|
|
562
|
+
kwargs.update(_instances)
|
|
563
|
+
callable(*args, **kwargs)
|
|
564
|
+
|
|
565
|
+
return argument_handler
|
|
566
|
+
|
|
567
|
+
return decorator
|
|
568
|
+
|
|
569
|
+
@classmethod
|
|
570
|
+
def settings(cls, name: str, default: T = None) -> Union[T, Any]:
|
|
571
|
+
return getattr(cls._settings, name, default)
|
|
572
|
+
|
|
573
|
+
#################################################
|
|
574
|
+
# AUTOMATION #
|
|
575
|
+
#################################################
|
|
576
|
+
|
|
577
|
+
@classmethod
|
|
578
|
+
def regenerate(cls):
|
|
579
|
+
if not cls._regenerating:
|
|
580
|
+
cls._regenerating = True
|
|
581
|
+
from jaseci.svc.kube_svc import KubeService
|
|
582
|
+
|
|
583
|
+
if cls._has_db:
|
|
584
|
+
from jaseci.utils.actions.actions_manager import ActionManager
|
|
585
|
+
|
|
586
|
+
while cls._regeneration_queues:
|
|
587
|
+
regeneration_queue = cls._regeneration_queues.pop(0)
|
|
588
|
+
service = cls.svc(regeneration_queue)
|
|
589
|
+
kube = cls.svc("kube", KubeService)
|
|
590
|
+
|
|
591
|
+
if (
|
|
592
|
+
not service.is_running()
|
|
593
|
+
and service.enabled
|
|
594
|
+
and service.automated
|
|
595
|
+
):
|
|
596
|
+
if service.manifest and kube.is_running():
|
|
597
|
+
pod_name = ""
|
|
598
|
+
old_config_map = deepcopy(
|
|
599
|
+
service.manifest_meta.get("__OLD_CONFIG__", {})
|
|
600
|
+
)
|
|
601
|
+
unsafe_paraphrase = service.manifest_meta.get(
|
|
602
|
+
"__UNSAFE_PARAPHRASE__", ""
|
|
603
|
+
)
|
|
604
|
+
for kind, confs in service.manifest.items():
|
|
605
|
+
for conf in confs:
|
|
606
|
+
name = conf["metadata"]["name"]
|
|
607
|
+
_confs = old_config_map.get(kind, {})
|
|
608
|
+
if name in _confs.keys():
|
|
609
|
+
_confs.pop(name)
|
|
610
|
+
|
|
611
|
+
if kind == "Service":
|
|
612
|
+
pod_name = name
|
|
613
|
+
res = kube.read(
|
|
614
|
+
kind,
|
|
615
|
+
name,
|
|
616
|
+
namespace=kube.resolve_namespace(conf),
|
|
617
|
+
)
|
|
618
|
+
if (
|
|
619
|
+
hasattr(res, "status")
|
|
620
|
+
and res.status == 404
|
|
621
|
+
and conf
|
|
622
|
+
):
|
|
623
|
+
kube.create(kind, name, conf)
|
|
624
|
+
elif (
|
|
625
|
+
not isinstance(res, ApiException)
|
|
626
|
+
and res.metadata
|
|
627
|
+
):
|
|
628
|
+
if res.metadata.labels:
|
|
629
|
+
config_version = res.metadata.labels.get(
|
|
630
|
+
"config_version", 1
|
|
631
|
+
)
|
|
632
|
+
else:
|
|
633
|
+
config_version = 1
|
|
634
|
+
|
|
635
|
+
if config_version != conf.get("metadata").get(
|
|
636
|
+
"labels", {}
|
|
637
|
+
).get("config_version", 1):
|
|
638
|
+
kube.patch(kind, name, conf)
|
|
639
|
+
|
|
640
|
+
if (
|
|
641
|
+
old_config_map
|
|
642
|
+
and type(old_config_map) is dict
|
|
643
|
+
and kind in old_config_map
|
|
644
|
+
and name in old_config_map[kind].keys()
|
|
645
|
+
):
|
|
646
|
+
old_config_map.get(kind, {}).pop(name)
|
|
647
|
+
|
|
648
|
+
for to_be_removed in old_config_map.get(
|
|
649
|
+
kind, {}
|
|
650
|
+
).keys():
|
|
651
|
+
res = kube.read(
|
|
652
|
+
kind,
|
|
653
|
+
to_be_removed,
|
|
654
|
+
namespace=kube.resolve_namespace(
|
|
655
|
+
old_config_map["kind"][to_be_removed]
|
|
656
|
+
),
|
|
657
|
+
)
|
|
658
|
+
if (
|
|
659
|
+
not isinstance(res, ApiException)
|
|
660
|
+
and res.metadata
|
|
661
|
+
):
|
|
662
|
+
if kind not in cls.settings(
|
|
663
|
+
"UNSAFE_KINDS"
|
|
664
|
+
) or unsafe_paraphrase == cls.settings(
|
|
665
|
+
"UNSAFE_PARAPHRASE"
|
|
666
|
+
):
|
|
667
|
+
kube.delete(kind, to_be_removed)
|
|
668
|
+
else:
|
|
669
|
+
logger.info(
|
|
670
|
+
f"You don't have permission to delete `{kind}` for `{to_be_removed}` with namespace `{kube.namespace}`!"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
if kube.is_pod_running(pod_name):
|
|
674
|
+
logger.info(
|
|
675
|
+
f"Pod state is running. Trying to Restart {regeneration_queue}..."
|
|
676
|
+
)
|
|
677
|
+
cls.svc_reset(regeneration_queue)
|
|
678
|
+
else:
|
|
679
|
+
cls._regeneration_queues.append(regeneration_queue)
|
|
680
|
+
else:
|
|
681
|
+
cls.svc_reset(regeneration_queue)
|
|
682
|
+
sleep(1)
|
|
683
|
+
|
|
684
|
+
action_manager = cls.get("action_manager", ActionManager)
|
|
685
|
+
action_manager.optimize(jsorc_interval=cls._backoff_interval)
|
|
686
|
+
action_manager.record_system_state()
|
|
687
|
+
else:
|
|
688
|
+
kube = cls.svc("kube", KubeService)
|
|
689
|
+
dbrc = cls.settings("DB_REGEN_CONFIG")
|
|
690
|
+
while not cls._has_db or not kube.terminate_jaseci(dbrc["pod"]):
|
|
691
|
+
for kind, confs in cls.settings("DB_REGEN_MANIFEST", {}).items():
|
|
692
|
+
for conf in confs:
|
|
693
|
+
name = conf["metadata"]["name"]
|
|
694
|
+
res = kube.read(kind, name)
|
|
695
|
+
if hasattr(res, "status") and res.status == 404 and conf:
|
|
696
|
+
kube.create(kind, name, conf)
|
|
697
|
+
sleep(1)
|
|
698
|
+
cls.db_check()
|
|
699
|
+
|
|
700
|
+
cls._regenerating = False
|
|
701
|
+
|
|
702
|
+
@classmethod
|
|
703
|
+
def db_check(cls):
|
|
704
|
+
if not cls._has_db:
|
|
705
|
+
try:
|
|
706
|
+
dbrc = cls.settings("DB_REGEN_CONFIG")
|
|
707
|
+
if dbrc["enabled"]:
|
|
708
|
+
connection = psycopg2.connect(
|
|
709
|
+
host=dbrc["host"],
|
|
710
|
+
dbname=dbrc["db"],
|
|
711
|
+
user=dbrc["user"],
|
|
712
|
+
password=dbrc["password"],
|
|
713
|
+
port=dbrc["port"],
|
|
714
|
+
)
|
|
715
|
+
connection.close()
|
|
716
|
+
cls._has_db = True
|
|
717
|
+
except Exception:
|
|
718
|
+
cls._has_db = False
|
|
719
|
+
return cls._has_db
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def interval_check(signum, frame):
|
|
723
|
+
JsOrc.regenerate()
|
|
724
|
+
logger.info(
|
|
725
|
+
f"Backing off for {JsOrc._backoff_interval} seconds before the next interval check..."
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# wait interval_check to be finished before decrement
|
|
729
|
+
JsOrc._running_interval -= 1
|
|
730
|
+
JsOrc.push_interval(JsOrc._backoff_interval)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
signal.signal(signal.SIGALRM, interval_check)
|