Skip to content

Commit 506dafd

Browse files
committed
feat(api): Add docker compose service for API server.
1 parent f7a978c commit 506dafd

File tree

12 files changed

+151
-25
lines changed

12 files changed

+151
-25
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/api-server/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ futures = "0.3.31"
2020
mongodb = "3.3.0"
2121
num_enum = "0.7.5"
2222
rmp-serde = "1.3.0"
23+
secrecy = "0.10.3"
2324
serde = { version = "1.0.228", features = ["derive"] }
2425
serde_json = "1.0.145"
2526
sqlx = { version = "0.8.6", features = ["runtime-tokio", "mysql"] }
2627
thiserror = "2.0.17"
2728
tokio = { version = "1.48.0", features = ["full"] }
2829
tracing = "0.1.41"
2930
tracing-appender = "0.2.3"
30-
tracing-subscriber = { version = "0.3.20", features = ["json", "env-filter"] }
31+
tracing-subscriber = { version = "0.3.20", features = ["json", "env-filter", "fmt", "std"] }

components/api-server/src/bin/api_server.rs

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,59 @@ use clp_rust_utils::{clp_config::package, serde::yaml};
1616
use futures::{Stream, StreamExt};
1717
use thiserror::Error;
1818
use tracing_appender::rolling::{RollingFileAppender, Rotation};
19-
use tracing_subscriber::{self};
19+
use tracing_subscriber::{self, fmt::writer::Tee};
2020

2121
#[derive(Parser)]
2222
#[command(version, about = "API Server for CLP.")]
23-
struct Args {}
23+
struct Args {
24+
#[arg(long)]
25+
config: String,
26+
27+
#[arg(long)]
28+
host: Option<String>,
29+
30+
#[arg(long)]
31+
port: Option<u16>,
32+
}
2433

2534
#[tokio::main]
2635
async fn main() -> anyhow::Result<()> {
27-
let _ = Args::parse();
28-
let home = std::env::var("CLP_HOME").context("Expect `CLP_HOME` env variable")?;
29-
let home = std::path::Path::new(&home);
36+
let args = Args::parse();
3037

31-
let config_path = home.join(package::DEFAULT_CONFIG_FILE_PATH);
32-
let config: package::config::Config = yaml::from_path(&config_path).context(format!(
38+
let config_path = std::path::Path::new(args.config.as_str());
39+
let config: package::config::Config = yaml::from_path(config_path).context(format!(
3340
"Config file {} does not exist",
3441
config_path.display()
3542
))?;
3643

37-
let file_appender = RollingFileAppender::new(
38-
Rotation::HOURLY,
39-
home.join(&config.logs_directory).join("api_server"),
40-
"api_server.log",
41-
);
44+
let logs_directory =
45+
std::env::var("CLP_LOGS_DIR").context("Expect `CLP_LOGS_DIR` environment variable.")?;
46+
let logs_directory = std::path::Path::new(logs_directory.as_str());
47+
let file_appender =
48+
RollingFileAppender::new(Rotation::HOURLY, logs_directory, "api_server.log");
4249
let (non_blocking_writer, _guard) = tracing_appender::non_blocking(file_appender);
4350
tracing_subscriber::fmt()
4451
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
4552
.with_ansi(false)
46-
.with_writer(non_blocking_writer)
53+
.with_writer(Tee::new(std::io::stdout, non_blocking_writer))
4754
.init();
4855

49-
let credentials_path = home.join(package::DEFAULT_CREDENTIALS_FILE_PATH);
50-
let credentials: package::credentials::Credentials = yaml::from_path(&credentials_path)
51-
.context(format!(
52-
"Credentials file {} does not exist",
53-
credentials_path.display()
54-
))?;
55-
56-
let addr = format!("{}:{}", &config.api_server.host, &config.api_server.port);
56+
let credentials = package::credentials::Credentials {
57+
database: package::credentials::Database {
58+
password: secrecy::SecretString::new(
59+
std::env::var("CLP_DB_PASS")
60+
.context("Expect `CLP_DB_PASS` env variable")?
61+
.into_boxed_str(),
62+
),
63+
user: std::env::var("CLP_DB_USER").context("Expect `CLP_DB_USER` env variable")?,
64+
},
65+
};
66+
67+
let addr = format!(
68+
"{}:{}",
69+
args.host.unwrap_or_else(|| config.api_server.host.clone()),
70+
args.port.unwrap_or(config.api_server.port)
71+
);
5772
let listener = tokio::net::TcpListener::bind(&addr)
5873
.await
5974
.context(format!("Cannot listen to {addr}"))?;
@@ -72,9 +87,15 @@ async fn main() -> anyhow::Result<()> {
7287
tracing::info!("Server started at {addr}");
7388
axum::serve(listener, app)
7489
.with_graceful_shutdown(async {
75-
tokio::signal::ctrl_c()
76-
.await
77-
.expect("failed to listen for event");
90+
let mut sigterm =
91+
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
92+
.expect("failed to listen for SIGTERM");
93+
tokio::select! {
94+
_ = sigterm.recv() => {
95+
}
96+
_ = tokio::signal::ctrl_c()=> {
97+
}
98+
}
7899
})
79100
.await?;
80101
Ok(())

components/clp-package-utils/clp_package_utils/controller.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any, Optional
1212

1313
from clp_py_utils.clp_config import (
14+
API_SERVER_COMPONENT_NAME,
1415
AwsAuthType,
1516
CLPConfig,
1617
COMPRESSION_JOBS_TABLE_NAME,
@@ -619,6 +620,30 @@ def _set_up_env_for_garbage_collector(self) -> EnvVarsDict:
619620

620621
return env_vars
621622

623+
def _set_up_env_for_api_server(self) -> EnvVarsDict:
624+
"""
625+
Sets up environment variables and directories for the API server component.
626+
627+
:return: Dictionary of environment variables necessary to launch the component.
628+
"""
629+
component_name = API_SERVER_COMPONENT_NAME
630+
631+
logger.info(f"Setting up environment for {component_name}...")
632+
633+
logs_dir = self._clp_config.logs_directory / component_name
634+
resolved_logs_dir = resolve_host_path_in_container(logs_dir)
635+
resolved_logs_dir.mkdir(parents=True, exist_ok=True)
636+
637+
env_vars = EnvVarsDict()
638+
639+
# Connection config
640+
env_vars |= {
641+
"CLP_API_SERVER_HOST": _get_ip_from_hostname(self._clp_config.api_server.host),
642+
"CLP_API_SERVER_PORT": str(self._clp_config.api_server.port),
643+
}
644+
645+
return env_vars
646+
622647
def _read_and_update_settings_json(
623648
self, settings_file_path: pathlib.Path, updates: dict[str, Any]
624649
) -> dict[str, Any]:
@@ -747,6 +772,7 @@ def set_up_env(self) -> None:
747772
env_vars |= self._set_up_env_for_webui(container_clp_config)
748773
env_vars |= self._set_up_env_for_mcp_server()
749774
env_vars |= self._set_up_env_for_garbage_collector()
775+
env_vars |= self._set_up_env_for_api_server()
750776

751777
# Write the environment variables to the `.env` file.
752778
with open(f"{self._clp_home}/.env", "w") as env_file:

components/clp-py-utils/clp_py_utils/clp_config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
WEBUI_COMPONENT_NAME = "webui"
4040
MCP_SERVER_COMPONENT_NAME = "mcp_server"
4141
GARBAGE_COLLECTOR_COMPONENT_NAME = "garbage_collector"
42+
API_SERVER_COMPONENT_NAME = "api_server"
4243

4344
# Action names
4445
ARCHIVE_MANAGER_ACTION_NAME = "archive_manager"
@@ -592,6 +593,19 @@ class GarbageCollector(BaseModel):
592593
sweep_interval: SweepInterval = SweepInterval()
593594

594595

596+
class QueryJobPollingConfig(BaseModel):
597+
initial_backoff_ms: int = Field(default=100, alias="initial_backoff")
598+
599+
max_backoff_ms: int = Field(default=5000, alias="max_backoff")
600+
601+
602+
class ApiServer(BaseModel):
603+
host: str = "localhost"
604+
port: int = 3001
605+
query_job_polling: QueryJobPollingConfig = QueryJobPollingConfig()
606+
default_max_num_query_results: int = 1000
607+
608+
595609
class Presto(BaseModel):
596610
DEFAULT_PORT: ClassVar[int] = 8080
597611

@@ -627,6 +641,7 @@ class CLPConfig(BaseModel):
627641
query_worker: QueryWorker = QueryWorker()
628642
webui: WebUi = WebUi()
629643
garbage_collector: GarbageCollector = GarbageCollector()
644+
api_server: ApiServer = ApiServer()
630645
credentials_file_path: SerializablePath = CLP_DEFAULT_CREDENTIALS_FILE_PATH
631646

632647
mcp_server: Optional[McpServer] = None

components/clp-rust-utils/src/clp_config/package/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ impl Default for ApiServer {
7575
}
7676

7777
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
78+
#[serde(default)]
7879
pub struct QueryJobPollingConfig {
7980
#[serde(rename = "initial_backoff")]
8081
pub initial_backoff_ms: u64,

components/package-template/src/etc/clp-config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@
118118
# archive: 60
119119
# search_result: 30
120120
#
121+
## API server config
122+
#api_server:
123+
# host: "localhost"
124+
# port: 3001
125+
# default_max_num_query_results: 1000
126+
# query_job_polling:
127+
# initial_backof_ms: 100
128+
# max_backoff_ms: 5000
129+
#
121130
## Presto client config
122131
#presto: null
123132
#

taskfile.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ vars:
3030
G_PYTHON_LIBS_DIR: "{{.G_BUILD_DIR}}/python-libs"
3131
G_WEBUI_BUILD_DIR: "{{.G_BUILD_DIR}}/webui"
3232
G_SPIDER_BUILD_DIR: "{{.G_BUILD_DIR}}/spider"
33+
G_RUST_BUILD_DIR: "{{.G_BUILD_DIR}}/rust"
3334

3435
# Taskfile paths
3536
G_UTILS_TASKFILE: "{{.ROOT_DIR}}/tools/yscope-dev-utils/exports/taskfiles/utils/utils.yaml"
@@ -73,6 +74,7 @@ tasks:
7374
vars:
7475
COMPONENT: "job-orchestration"
7576
- task: "clean-webui"
77+
- task: "clean-rust"
7678
- task: "tests:integration:cache-clear"
7779

7880
clean-core:
@@ -103,6 +105,10 @@ tasks:
103105
- "rm -rf '{{.G_WEBUI_SRC_DIR}}/server/node_modules'"
104106
- "rm -rf '{{.G_WEBUI_SRC_DIR}}/yscope-log-viewer/node_modules'"
105107

108+
clean-rust:
109+
cmds:
110+
- "rm -rf '{{.G_RUST_BUILD_DIR}}'"
111+
106112
package:
107113
vars:
108114
CHECKSUM_FILE: "{{.G_PACKAGE_CHECKSUM_FILE}}"
@@ -191,6 +197,13 @@ tasks:
191197
JOBS: "{{.G_CPP_MAX_PARALLELISM_PER_BUILD_TASK}}"
192198
TARGETS: ["clg", "clo", "clp", "clp-s", "indexer", "log-converter", "reducer-server"]
193199

200+
rust:
201+
deps: ["toolchains:rust"]
202+
dir: "{{.ROOT_DIR}}"
203+
cmd: |-
204+
. "$HOME/.cargo/env"
205+
cargo build --release --bins --target-dir {{.G_RUST_BUILD_DIR}}
206+
194207
clp-mcp-server:
195208
- task: "uv-component"
196209
vars:
@@ -218,6 +231,7 @@ tasks:
218231
- "init"
219232
- "python-libs"
220233
- "webui"
234+
- "rust"
221235

222236
webui:
223237
env:

tools/deployment/package/docker-compose-all.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,30 @@ services:
486486
"-f",
487487
"http://mcp_server:8000/health"
488488
]
489+
490+
api-server:
491+
<<: *service_defaults
492+
hostname: "api_server"
493+
environment:
494+
CLP_LOGS_DIR: "/var/log/api_server"
495+
CLP_DB_PASS: "${CLP_DB_PASS:?Please set a value.}"
496+
CLP_DB_USER: "${CLP_DB_USER:?Please set a value.}"
497+
RUST_LOG: "TRACE"
498+
ports:
499+
- host_ip: "${CLP_API_SERVER_HOST:-127.0.0.1}"
500+
published: "${CLP_API_SERVER_PORT:-3001}"
501+
target: 3001
502+
volumes:
503+
- *volume_clp_config_readonly
504+
- *volume_clp_logs
505+
depends_on:
506+
db-table-creator:
507+
condition: "service_completed_successfully"
508+
results-cache-indices-creator:
509+
condition: "service_completed_successfully"
510+
command: [
511+
"/opt/clp/bin/api_server",
512+
"--host", "0.0.0.0",
513+
"--port", "3001",
514+
"--config", "/etc/clp-config.yml"
515+
]

tools/deployment/package/docker-compose-base.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,8 @@ services:
5353
extends:
5454
file: "docker-compose-all.yaml"
5555
service: "garbage-collector"
56+
57+
api-server:
58+
extends:
59+
file: "docker-compose-all.yaml"
60+
service: "api-server"

0 commit comments

Comments
 (0)