Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions bin/nanocld/specs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6502,6 +6502,11 @@ components:
items:
type: string
description: Path to extra config file to include
Timeout:
oneOf:
- type: 'null'
- $ref: '#/components/schemas/ProxyTimeout'
description: Timeout configuration (connect, client, server in seconds)
additionalProperties: false
ProxyRuleStream:
type: object
Expand Down Expand Up @@ -6570,6 +6575,26 @@ components:
enum:
- Tcp
- Udp
ProxyTimeout:
type: object
description: Proxy timeout configuration
properties:
Connect:
type:
- string
- 'null'
description: Timeout for backend connection (default 5s)
Client:
type:
- string
- 'null'
description: Timeout for client inactivity (default 50s)
Server:
type:
- string
- 'null'
description: Timeout for server inactivity (default 50s)
additionalProperties: false
RegistryServiceConfig:
type: object
description: RegistryServiceConfig stores daemon registry services configuration.
Expand Down
25 changes: 25 additions & 0 deletions bin/ncproxy/specs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ components:
items:
type: string
description: Path to extra config file to include
Timeout:
oneOf:
- type: 'null'
- $ref: '#/components/schemas/ProxyTimeout'
description: Timeout configuration (connect, client, server in seconds)
additionalProperties: false
ProxyRuleStream:
type: object
Expand Down Expand Up @@ -407,6 +412,26 @@ components:
enum:
- Tcp
- Udp
ProxyTimeout:
type: object
description: Proxy timeout configuration
properties:
Connect:
type:
- string
- 'null'
description: Timeout for backend connection (default 5s)
Client:
type:
- string
- 'null'
description: Timeout for client inactivity (default 50s)
Server:
type:
- string
- 'null'
description: Timeout for server inactivity (default 50s)
additionalProperties: false
ResourceProxyRule:
type: object
description: Define proxy rules to apply
Expand Down
18 changes: 11 additions & 7 deletions bin/ncproxy/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ use clap::Parser;

#[derive(Parser)]
pub struct Cli {
/// Path to nginx config directory
#[clap(long, default_value = "/etc/nginx")]
pub nginx_dir: String,
/// Path to haproxy config directory
#[clap(
long = "haproxy-dir",
alias = "nginx-dir",
default_value = "/etc/haproxy"
)]
pub haproxy_dir: String,
/// Path to state directory
#[clap(long)]
pub state_dir: String,
Expand All @@ -18,15 +22,15 @@ mod tests {
fn parse() {
let args = Cli::parse_from([
"ncproxy",
"--nginx-dir",
"/test/nginx",
"--haproxy-dir",
"/test/haproxy",
"--state-dir",
"/test/state",
]);
assert_eq!(args.nginx_dir, "/test/nginx");
assert_eq!(args.haproxy_dir, "/test/haproxy");
assert_eq!(args.state_dir, "/test/state");
let args = Cli::parse_from(["ncproxy", "--state-dir", "/test/state"]);
assert_eq!(args.nginx_dir, "/etc/nginx");
assert_eq!(args.haproxy_dir, "/etc/haproxy");
assert_eq!(args.state_dir, "/test/state");
let _ = Cli::try_parse();
}
Expand Down
8 changes: 4 additions & 4 deletions bin/ncproxy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/// # ncproxy
///
/// The program in charge of managing writing proxy configuration.
/// It's based on nginx and use the nanocld api to get the configuration wanted by the user.
/// It's now based on HAProxy and uses the nanocld api to get the configuration wanted by the user.
///
/// It work by doing 4 main tasks:
/// - Create a new rule in nginx when a resource `ncproxy.io/rule` is created
/// - Delete a new rule in nginx when a resource `ncproxy.io/rule` is deleted
/// - Create a new rule in HAProxy when a resource `ncproxy.io/rule` is created
/// - Delete a rule in HAProxy when a resource `ncproxy.io/rule` is deleted
/// - Watch nanocld events for resource, cargo and vm change to update proxy rules accordingly
/// - Send a reload task to nginx when a rule is created, deleted or updated
/// - Send a reload task to HAProxy when a rule is created, deleted or updated
///
use clap::Parser;

Expand Down
2 changes: 2 additions & 0 deletions bin/ncproxy/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod runtime_state;
mod store;
mod system;
mod template;

pub use runtime_state::*;
pub use store::*;
pub use system::*;
pub use template::*;
48 changes: 48 additions & 0 deletions bin/ncproxy/src/models/runtime_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use serde::{Deserialize, Serialize};

/// Persistent state for a single proxy rule backend
/// Used to recreate backends after HAProxy restart
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendState {
/// Backend name (e.g., "bk_deploy-example.global.c-9000")
pub name: String,
/// Mode: "http" or "tcp"
pub mode: String,
/// Server definitions
pub servers: Vec<ServerState>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerState {
/// Server name (e.g., "deploy-example.global.c-9000_1")
pub name: String,
/// Server address:port
pub address: String,
pub port: u16,
/// Additional options (ssl, alpn, check, etc.)
pub options: String,
}

/// Persistent state for domain routing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainMapEntry {
/// Domain name (lowercase)
pub domain: String,
/// Backend name
pub backend: String,
}

/// Complete state file for a proxy rule
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyRuleState {
/// Rule name
pub name: String,
/// HTTP bind addresses
pub http_binds: Vec<String>,
/// HTTPS bind addresses
pub https_binds: Vec<String>,
/// All backends for this rule
pub backends: Vec<BackendState>,
/// Domain -> backend mappings
pub domain_mappings: Vec<DomainMapEntry>,
}
22 changes: 11 additions & 11 deletions bin/ncproxy/src/models/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use nanocld_client::stubs::proxy::ProxyRule;
/// * Site for HTTP/HTTPS
/// * Stream for TCP/UDP
#[derive(Debug)]
pub enum NginxRuleKind {
pub enum ProxyRuleKind {
Site,
Stream,
}

/// Implement Display for RuleKind for better display message
impl std::fmt::Display for NginxRuleKind {
impl std::fmt::Display for ProxyRuleKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Site => write!(f, "Site"),
Expand All @@ -24,7 +24,7 @@ impl std::fmt::Display for NginxRuleKind {
}

/// Implement From<ProxyRule> for RuleKind to convert ProxyRule to RuleKind
impl From<ProxyRule> for NginxRuleKind {
impl From<ProxyRule> for ProxyRuleKind {
fn from(rule: ProxyRule) -> Self {
match rule {
ProxyRule::Http(_) => Self::Site,
Expand All @@ -33,7 +33,7 @@ impl From<ProxyRule> for NginxRuleKind {
}
}

impl FromStr for NginxRuleKind {
impl FromStr for ProxyRuleKind {
type Err = IoError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand All @@ -60,14 +60,14 @@ impl Store {
}
}

fn gen_path(&self, name: &str, kind: &NginxRuleKind) -> (String, String) {
fn gen_path(&self, name: &str, kind: &ProxyRuleKind) -> (String, String) {
let dir = &self.dir;
match kind {
NginxRuleKind::Site => (
format!("{dir}/sites-available/{name}.conf"),
format!("{dir}/sites-enabled/{name}.conf"),
ProxyRuleKind::Site => (
format!("{dir}/routes-available/{name}.conf"),
format!("{dir}/routes-enabled/{name}.conf"),
),
NginxRuleKind::Stream => (
ProxyRuleKind::Stream => (
format!("{dir}/streams-available/{name}.conf"),
format!("{dir}/streams-enabled/{name}.conf"),
),
Expand All @@ -78,7 +78,7 @@ impl Store {
&self,
name: &str,
data: &str,
kind: &NginxRuleKind,
kind: &ProxyRuleKind,
) -> IoResult<()> {
let path = self.gen_path(name, kind);
tokio::fs::write(&path.0, data).await.map_err(|err| {
Expand All @@ -90,7 +90,7 @@ impl Store {
Ok(())
}

pub async fn delete_conf_file(&self, name: &str, kind: &NginxRuleKind) {
pub async fn delete_conf_file(&self, name: &str, kind: &ProxyRuleKind) {
let path = self.gen_path(name, kind);
let _ = tokio::fs::remove_file(&path.0).await;
let _ = tokio::fs::remove_file(&path.1).await;
Expand Down
16 changes: 11 additions & 5 deletions bin/ncproxy/src/models/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct SystemState {
pub store: Store,
pub client: NanocldClient,
pub event_emitter: EventEmitter,
pub nginx_dir: String,
pub haproxy_dir: String,
}

pub type SystemStateRef = Arc<SystemState>;
Expand All @@ -29,15 +29,17 @@ pub enum SystemEventKind {

struct SystemEventInner {
client: NanocldClient,
state_dir: String,
task: ntex::rt::JoinHandle<IoResult<()>>,
}

pub struct SystemEvent(SystemEventInner);

impl SystemEvent {
pub fn new(client: &NanocldClient) -> Self {
pub fn new(client: &NanocldClient, state_dir: String) -> Self {
Self(SystemEventInner {
client: client.clone(),
state_dir,
task: rt::spawn(async move { Ok::<_, IoError>(()) }),
})
}
Expand All @@ -49,9 +51,13 @@ impl SystemEvent {
abort_handle.abort();
}
let client = self.0.client.clone();
let state_dir = self.0.state_dir.clone();
self.0.task = rt::spawn(async move {
// Short debounce so multiple apply/delete batch into one reload
ntex::time::sleep(std::time::Duration::from_millis(750)).await;
if let Err(err) = utils::nginx::reload(&client).await {
// Only reload here. All file writes, map sync, and validation
// must have been done before emitting the reload event.
if let Err(err) = utils::haproxy::reload(&client, &state_dir).await {
log::warn!("system: {err}");
}
Ok::<_, IoError>(())
Expand All @@ -64,12 +70,12 @@ pub struct EventEmitter(pub Arc<mpsc::UnboundedSender<SystemEventKind>>);

impl EventEmitter {
/// Create a new thread with it's own event loop and return an emitter to send events to it
pub fn new(client: &NanocldClient) -> Self {
pub fn new(client: &NanocldClient, state_dir: String) -> Self {
let (tx, mut rx) = mpsc::unbounded();
let client = client.clone();
rt::Arbiter::new().exec_fn(move || {
ntex::rt::spawn(async move {
let mut local_event = SystemEvent::new(&client);
let mut local_event = SystemEvent::new(&client, state_dir);
while let Some(e) = rx.next().await {
local_event.handle(e);
}
Expand Down
2 changes: 1 addition & 1 deletion bin/ncproxy/src/models/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl Template<'_> {
}

pub const CONF_TEMPLATE: &Template = &Template {
data: include_str!("templates/nginx.conf"),
data: include_str!("templates/haproxy.cfg"),
};

pub const STREAM_TEMPLATE: &Template = &Template {
Expand Down
10 changes: 0 additions & 10 deletions bin/ncproxy/src/models/templates/default_srv.conf

This file was deleted.

26 changes: 26 additions & 0 deletions bin/ncproxy/src/models/templates/haproxy.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
global
log stdout format raw local0 info
# Duplicate logs to local unix socket for persistence
log {{ state_dir }}/log/haproxy.sock len 65535 format raw local0 info
maxconn 2048
# Runtime API socket for dynamic backend management
stats socket {{ state_dir }}/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
# Master CLI socket for reloads in worker mode
master-worker
{% if ssl_dh_param %} ssl-dh-param-file {{ ssl_dh_param }}
{% endif %}

defaults
log global
mode http
log-format '{"remote_addr":"%ci","time_local":"%t","request_method":"%HM","uri":"%HU","server_protocol":"%HV","status":%ST,"body_bytes_sent":%B,"frontend":"%f","backend":"%b","server":"%s","tq":%Tq,"tw":%Tw,"tc":%Tc,"tr":%Tr,"tt":%Tt,"host":"%[capture.req.hdr(0)]","upstream_addr":"%si:%sp","request_time":%Tt}'
option dontlognull
option forwardfor
timeout connect {{ timeout_connect | default: "5s" }}
timeout client {{ timeout_client | default: "50s" }}
timeout server {{ timeout_server | default: "50s" }}

# HAProxy config sections generated dynamically
# Sites and streams are written directly into enabled directories

Loading
Loading