|
7 | 7 | import os
|
8 | 8 | import threading
|
9 | 9 |
|
| 10 | +import requests |
10 | 11 | from jupyter_core.paths import jupyter_data_dir
|
11 |
| -from traitlets import Bool, Unicode, default |
| 12 | +from requests.auth import HTTPBasicAuth, HTTPDigestAuth |
| 13 | +from traitlets import Bool, CaselessStrEnum, Unicode, default |
12 | 14 | from traitlets.config.configurable import LoggingConfigurable
|
13 | 15 |
|
14 | 16 | kernels_lock = threading.Lock()
|
@@ -385,3 +387,150 @@ def _get_sessions_loc(self):
|
385 | 387 | if not os.path.exists(path):
|
386 | 388 | os.makedirs(path, 0o755)
|
387 | 389 | return path
|
| 390 | + |
| 391 | + |
| 392 | +class WebhookKernelSessionManager(KernelSessionManager): |
| 393 | + """ |
| 394 | + Performs kernel session persistence operations against URL provided (EG_WEBHOOK_URL). The URL must have 4 endpoints |
| 395 | + associated with it. 1 delete endpoint that takes a list of kernel ids in the body, 1 post endpoint that takes kernels id as a |
| 396 | + url param and the kernel session as the body, 1 get endpoint that returns all kernel sessions, and 1 get endpoint that returns |
| 397 | + a specific kernel session based on kernel id as url param. |
| 398 | + """ |
| 399 | + |
| 400 | + # Webhook URL |
| 401 | + webhook_url_env = "EG_WEBHOOK_URL" |
| 402 | + webhook_url = Unicode( |
| 403 | + config=True, |
| 404 | + allow_none=True, |
| 405 | + help="""URL endpoint for webhook kernel session manager""", |
| 406 | + ) |
| 407 | + |
| 408 | + @default("webhook_url") |
| 409 | + def webhook_url_default(self): |
| 410 | + return os.getenv(self.webhook_url_env, None) |
| 411 | + |
| 412 | + # Webhook Username |
| 413 | + webhook_username_env = "EG_WEBHOOK_USERNAME" |
| 414 | + webhook_username = Unicode( |
| 415 | + config=True, |
| 416 | + allow_none=True, |
| 417 | + help="""Username for webhook kernel session manager API auth""", |
| 418 | + ) |
| 419 | + |
| 420 | + @default("webhook_username") |
| 421 | + def webhook_username_default(self): |
| 422 | + return os.getenv(self.webhook_username_env, None) |
| 423 | + |
| 424 | + # Webhook Password |
| 425 | + webhook_password_env = "EG_WEBHOOK_PASSWORD" |
| 426 | + webhook_password = Unicode( |
| 427 | + config=True, |
| 428 | + allow_none=True, |
| 429 | + help="""Password for webhook kernel session manager API auth""", |
| 430 | + ) |
| 431 | + |
| 432 | + @default("webhook_password") |
| 433 | + def webhook_password_default(self): |
| 434 | + return os.getenv(self.webhook_password_env, None) |
| 435 | + |
| 436 | + # Auth Type |
| 437 | + auth_type_env = "EG_AUTH_TYPE" |
| 438 | + auth_type = CaselessStrEnum( |
| 439 | + config=True, |
| 440 | + allow_none=True, |
| 441 | + values=["basic", "digest"], |
| 442 | + help="""Authentication type for webhook kernel session manager API. Either basic, digest or None""", |
| 443 | + ) |
| 444 | + |
| 445 | + @default("auth_type") |
| 446 | + def auth_type_default(self): |
| 447 | + return os.getenv(self.auth_type_env, None) |
| 448 | + |
| 449 | + def __init__(self, kernel_manager, **kwargs): |
| 450 | + super().__init__(kernel_manager, **kwargs) |
| 451 | + if self.enable_persistence: |
| 452 | + self.log.info("Webhook kernel session persistence activated") |
| 453 | + self.auth = "" |
| 454 | + if self.auth_type: |
| 455 | + if self.webhook_username and self.webhook_password: |
| 456 | + if self.auth_type == "basic": |
| 457 | + self.auth = HTTPBasicAuth(self.webhook_username, self.webhook_password) |
| 458 | + elif self.auth_type == "digest": |
| 459 | + self.auth = HTTPDigestAuth(self.webhook_username, self.webhook_password) |
| 460 | + elif self.auth_type is None: |
| 461 | + self.auth = "" |
| 462 | + else: |
| 463 | + self.log.error("No such option for auth_type/EG_AUTH_TYPE") |
| 464 | + else: |
| 465 | + self.log.error("Username and/or password aren't set") |
| 466 | + |
| 467 | + def delete_sessions(self, kernel_ids): |
| 468 | + """ |
| 469 | + Deletes kernel sessions from database |
| 470 | +
|
| 471 | + :param list of strings kernel_ids: A list of kernel ids |
| 472 | + """ |
| 473 | + if self.enable_persistence: |
| 474 | + response = requests.delete(self.webhook_url, auth=self.auth, json=kernel_ids) |
| 475 | + self.log.debug(f"Webhook kernel session deleting: {kernel_ids}") |
| 476 | + if response.status_code != 204: |
| 477 | + self.log.error(response.raise_for_status()) |
| 478 | + |
| 479 | + def save_session(self, kernel_id): |
| 480 | + """ |
| 481 | + Saves kernel session to database |
| 482 | +
|
| 483 | + :param string kernel_id: A kernel id |
| 484 | + """ |
| 485 | + if self.enable_persistence: |
| 486 | + if kernel_id is not None: |
| 487 | + temp_session = dict() |
| 488 | + temp_session[kernel_id] = self._sessions[kernel_id] |
| 489 | + body = KernelSessionManager.pre_save_transformation(temp_session) |
| 490 | + response = requests.post( |
| 491 | + f"{self.webhook_url}/{kernel_id}", auth=self.auth, json=body |
| 492 | + ) |
| 493 | + self.log.debug(f"Webhook kernel session saving: {kernel_id}") |
| 494 | + if response.status_code != 204: |
| 495 | + self.log.error(response.raise_for_status()) |
| 496 | + |
| 497 | + def load_sessions(self): |
| 498 | + """ |
| 499 | + Loads kernel sessions from database |
| 500 | + """ |
| 501 | + if self.enable_persistence: |
| 502 | + response = requests.get(self.webhook_url, auth=self.auth) |
| 503 | + if response.status_code == 200: |
| 504 | + kernel_sessions = response.content |
| 505 | + for kernel_session in kernel_sessions: |
| 506 | + self._load_session_from_response(kernel_session) |
| 507 | + else: |
| 508 | + self.log.error(response.raise_for_status()) |
| 509 | + |
| 510 | + def load_session(self, kernel_id): |
| 511 | + """ |
| 512 | + Loads a kernel session from database |
| 513 | +
|
| 514 | + :param string kernel_id: A kernel id |
| 515 | + """ |
| 516 | + if self.enable_persistence: |
| 517 | + if kernel_id is not None: |
| 518 | + response = requests.get(f"{self.webhook_url}/{kernel_id}", auth=self.auth) |
| 519 | + if response.status_code == 200: |
| 520 | + kernel_session = response.content |
| 521 | + self._load_session_from_response(kernel_session) |
| 522 | + else: |
| 523 | + self.log.error(response.raise_for_status()) |
| 524 | + |
| 525 | + def _load_session_from_response(self, kernel_session: dict): |
| 526 | + """ |
| 527 | + Loads kernel session to current session |
| 528 | +
|
| 529 | + :param dictionary kernel_session: Kernel session information |
| 530 | + """ |
| 531 | + self.log.debug("Loading saved session(s)") |
| 532 | + self._sessions.update( |
| 533 | + KernelSessionManager.post_load_transformation( |
| 534 | + json.loads(kernel_session)["kernel_session"] |
| 535 | + ) |
| 536 | + ) |
0 commit comments