Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Routes for Modules #528

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
95760b0
Panorama config
trueleo Sep 26, 2023
cecb5d1
Registration for external modules
trueleo Oct 4, 2023
c2f2906
fix
trueleo Oct 4, 2023
c3fd833
- save registration in parseable json
aldrinjenson Oct 5, 2023
b7edd96
Fix lock drop and pass all headers
trueleo Oct 5, 2023
eb2b8ec
filter out authorization header passed to module
aldrinjenson Oct 5, 2023
e0928c6
Add deregister for modules
aldrinjenson Oct 6, 2023
9744ac7
update registration flow based on new spec
aldrinjenson Oct 9, 2023
9bdfafc
Put module config in stream json
trueleo Oct 9, 2023
bb7b9cf
Fix
trueleo Oct 9, 2023
ea0a575
Fix
trueleo Oct 9, 2023
63de18a
add semver checking
aldrinjenson Oct 9, 2023
f99003c
Fix
trueleo Oct 9, 2023
4e6b260
Fix
trueleo Oct 9, 2023
2612dd4
Use id from path
trueleo Oct 9, 2023
668fd7c
pass in content type for config
aldrinjenson Oct 9, 2023
bea613c
fix issue with tail to proxy to module_path
aldrinjenson Oct 9, 2023
c10259a
revert change for dynami path
aldrinjenson Oct 9, 2023
d174a32
Merge branch 'main' into panorama_config
nitisht Oct 10, 2023
7f9215c
deepsource fix
aldrinjenson Oct 10, 2023
7029ada
Refactor
trueleo Oct 10, 2023
0311963
Validate Version
trueleo Oct 10, 2023
1ac4089
Update routes to accept module_name in path
aldrinjenson Oct 11, 2023
19fb1f8
Handle edge case when stream does not exist
aldrinjenson Oct 11, 2023
0743567
load registration into module registry upon startup.
aldrinjenson Oct 11, 2023
f8948e8
deepsource fix
aldrinjenson Oct 11, 2023
c380e7b
Update server/Cargo.toml
nitisht Oct 12, 2023
88f8dfb
add license
aldrinjenson Oct 12, 2023
6a21995
update workflow to test modules route with panorama
aldrinjenson Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions docker-compose-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ services:
ports:
- 9000
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ]
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 15s
timeout: 20s
retries: 5
Expand All @@ -46,7 +46,7 @@ services:
networks:
- parseable-internal
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/liveness" ]
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/liveness"]
interval: 15s
timeout: 20s
retries: 5
Expand All @@ -58,13 +58,42 @@ services:
delay: 20s
max_attempts: 3

panorama:
image: ghcr.io/aldrinjenson/panorama:main
networks:
- parseable-internal
depends_on:
- parseable
environment:
- PANORAMA_ADDRESS="http:0.0.0.0:5000"
- PANORAMA_ADMIN_USERNAME=pano_admin
- PANORAMA_ADMIN_PASSWORD=pano_admin
- PARSEABLE_USERNAME=parseableadmin # todo: make as env var and use in other jobs
- PARSEABLE_PASSWORD=parseableadmin
- PARSEABLE_BASE_URL="http://parseable:8000/api/v1"
deploy:
restart_policy:
condition: on-failure
delay: 20s
max_attempts: 3

quest:
image: ghcr.io/parseablehq/quest:main
command: ["load", "http://parseable:8000", "parseableadmin", "parseableadmin", "20"]
command: [
"load",
"http://parseable:8000",
"parseableadmin",
"parseableadmin",
"20",
"http:0.0.0.0:5000", # todo: make these into env var
pano_admin,
pano_admin,
]
networks:
- parseable-internal
depends_on:
- parseable
- panorama
deploy:
restart_policy:
condition: on-failure
Expand Down
131 changes: 131 additions & 0 deletions server/src/external_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Parseable Server (C) 2022 - 2023 Parseable, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

use std::{
aldrinjenson marked this conversation as resolved.
Show resolved Hide resolved
collections::HashMap,
sync::{Arc, RwLock},
};

use crate::storage::StorageMetadata;
use once_cell::sync::OnceCell;

pub static MODULE_REGISTRY: OnceCell<Arc<RwLock<ModuleRegistry>>> = OnceCell::new();

pub fn init(metadata: &StorageMetadata) {
let mut registry = ModuleRegistry::default();
registry.load_registry(metadata);

MODULE_REGISTRY
.set(Arc::new(RwLock::new(registry)))
.expect("Module Registry is only set once");
}

pub fn global_module_registry() -> Arc<RwLock<ModuleRegistry>> {
MODULE_REGISTRY
.get()
.expect("Module Registry initialized in main")
.clone()
}

#[derive(Debug, Default)]
pub struct ModuleRegistry {
inner: HashMap<String, Registration>,
}

impl ModuleRegistry {
pub fn load_registry(&mut self, metadata: &StorageMetadata) {
for (module_name, module) in &metadata.modules {
self.inner.insert(module_name.clone(), module.clone());
}
}
pub fn register(&mut self, module_name: String, module: Registration) {
self.inner.insert(module_name, module);
}

pub fn get(&self, id: &str) -> Option<&Registration> {
self.inner.get(id)
}

pub fn get_keys(&self) -> Vec<String> {
self.inner.keys().cloned().collect()
}

pub fn deregister(&mut self, module_id: &str) {
self.inner.remove(module_id);
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
pub struct StreamConfig {
pub path: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Registration {
pub version: String,
pub url: url::Url,
pub username: String,
pub password: String,
pub stream_config: StreamConfig,
pub routes: Vec<Route>,
}

impl Registration {
pub fn get_module_path(&self, path: &str, method: &http::Method) -> Option<String> {
self.routes
.iter()
.find(|x| x.server_path == path && method.eq(&x.method))
.map(|route| route.module_path.clone())
}
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
pub struct DeRegistration {
pub id: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Route {
pub server_path: String,
pub module_path: String,
pub method: Method,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "UPPERCASE")]
#[allow(clippy::upper_case_acronyms)]
pub enum Method {
GET,
PUT,
POST,
DELETE,
}

impl PartialEq<Method> for http::Method {
fn eq(&self, other: &Method) -> bool {
matches!(
(self, other),
(&http::Method::GET, &Method::GET)
| (&http::Method::PUT, &Method::PUT)
| (&http::Method::POST, &Method::POST)
| (&http::Method::DELETE, &Method::DELETE)
)
}
}
21 changes: 20 additions & 1 deletion server/src/handlers/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ use rustls::{Certificate, PrivateKey, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};

use crate::option::CONFIG;
use crate::rbac::role::Action;
use crate::{external_service, rbac::role::Action};

use self::middleware::{DisAllowRootUser, RouteExt};

mod about;
mod external;
mod health_check;
mod ingest;
mod llm;
Expand Down Expand Up @@ -286,6 +287,23 @@ pub fn configure_routes(
oauth_api = oauth_api.app_data(web::Data::from(client))
}

let external_services = web::scope("modules")
.service(resource("").route(web::get().to(external::list_modules)))
.service(
resource("{module}")
.route(web::put().to(external::register))
.route(web::delete().to(external::deregister)),
)
.service(
resource("{module}/config/{logstream}")
.route(web::get().to(external::get_config))
.route(web::put().to(external::put_config)),
)
.service(resource("{module}/{tail}*").to(external::router))
.app_data(web::Data::from(Arc::clone(
&external_service::global_module_registry(),
)));

// Deny request if username is same as the env variable P_USERNAME.
cfg.service(
// Base path "{url}/api/v1"
Expand Down Expand Up @@ -329,6 +347,7 @@ pub fn configure_routes(
.service(user_api)
.service(llm_query_api)
.service(oauth_api)
.service(external_services)
.service(role_api),
)
// GET "/" ==> Serve the static frontend directory
Expand Down
Loading
Loading