Skip to content

Commit 127323f

Browse files
authored
feat: compute DCU usage (#6)
1 parent 1b492d6 commit 127323f

File tree

14 files changed

+411
-173
lines changed

14 files changed

+411
-173
lines changed

.github/workflows/validate.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,8 @@ jobs:
3232
- name: Load Image into Kind
3333
run: kind load docker-image ext-cardano-dbsync:1.0 --name k8scluster
3434

35-
- name: Apply manifest
36-
run: kubectl apply -f test/manifest.yaml
37-
38-
- name: Wait containers is ready
39-
run: sleep 8;
40-
4135
- name: Apply manifests
42-
run: kubectl apply -f test/dbsyncport.yaml
36+
run: kubectl apply -f test
4337

44-
- name: Validate if controller was executed
45-
run: |
46-
username=$(kubectl describe dbsyncports.demeter.run --namespace project useraccess | grep -oP 'Username: \K\S+')
47-
password=$(kubectl describe dbsyncports.demeter.run --namespace project useraccess | grep -oP 'Password: \K\S+')
48-
if [ -z "$username" ] && [ -z "$password" ]; then echo "Error: controller not executed" && exit 1; fi
38+
- name: Validate controller
39+
run: ./test/validate-execution

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,13 @@ prometheus = "0.13.3"
2424
actix-web = "4.4.0"
2525

2626
[[bin]]
27-
doc = false
2827
name = "controller"
2928
path = "src/main.rs"
3029

3130
[[bin]]
32-
doc = false
3331
name = "crdgen"
3432
path = "src/crdgen.rs"
3533

3634
[lib]
37-
name = "controller"
3835
path = "src/lib.rs"
3936

README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,30 @@
22

33
This project is a Kubernetes custom controller to create users on dbsync's Postgres. This controller defines a new CRD DbSyncPort on Kubernetes and when the new users enable the External Dbsync, the Demiter will generate a manifest with the kind DbSyncPort and the controller will be watching for creating a new user on Postgres.
44

5+
> [!IMPORTANT]
6+
> The metrics collector uses the `pg_stat_statements` extension enabled on Postgres. To enable that extension follow the steps bellow.
7+
8+
- set pg_stat_statements at `shared_preload_libraries` on postgresql.conf
9+
```
10+
shared_preload_libraries = 'pg_stat_statements'
11+
```
12+
- create the extension on postgres
13+
```
14+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
15+
```
16+
517
## Environment
618

7-
| Key | Value |
8-
| -------------- | ------------------------------------- |
9-
| ADDR | 0.0.0.0:5000 |
10-
| DB_URL_MAINNET | postgres://user:password@host:post/db |
11-
| DB_URL_PREPROD | postgres://user:password@host:post/db |
12-
| DB_URL_PREVIEW | postgres://user:password@host:post/db |
19+
| Key | Value |
20+
| ---------------------- | ------------------------------------- |
21+
| ADDR | 0.0.0.0:5000 |
22+
| DB_URL_MAINNET | postgres://user:password@host:post/db |
23+
| DB_URL_PREPROD | postgres://user:password@host:post/db |
24+
| DB_URL_PREVIEW | postgres://user:password@host:post/db |
25+
| METRICS_DELAY | 30 |
26+
| DCU_PER_SECOND_MAINNET | 10 |
27+
| DCU_PER_SECOND_PREPROD | 10 |
28+
| DCU_PER_SECOND_PREVIEW | 10 |
1329

1430
## Commands
1531

src/controller.rs

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
use crate::{
2-
postgres::{self, user_already_exists, user_create, user_disable, user_enable},
3-
Config, Error, Metrics, Result,
4-
};
51
use futures::StreamExt;
62
use kube::{
73
api::{Patch, PatchParams},
@@ -20,6 +16,8 @@ use serde_json::json;
2016
use std::{sync::Arc, time::Duration};
2117
use tracing::error;
2218

19+
use crate::{postgres::Postgres, Config, Error, Metrics, State, Network};
20+
2321
pub static DB_SYNC_PORT_FINALIZER: &str = "dbsyncports.demeter.run";
2422

2523
struct Context {
@@ -36,25 +34,6 @@ impl Context {
3634
}
3735
}
3836
}
39-
#[derive(Clone, Default)]
40-
pub struct State {
41-
registry: prometheus::Registry,
42-
}
43-
impl State {
44-
pub fn metrics(&self) -> Vec<prometheus::proto::MetricFamily> {
45-
self.registry.gather()
46-
}
47-
}
48-
49-
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
50-
pub enum Network {
51-
#[serde(rename = "mainnet")]
52-
Mainnet,
53-
#[serde(rename = "preprod")]
54-
Preprod,
55-
#[serde(rename = "preview")]
56-
Preview,
57-
}
5837

5938
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
6039
#[kube(kind = "DbSyncPort", group = "demeter.run", version = "v1", namespaced)]
@@ -75,11 +54,7 @@ impl DbSyncPort {
7554
.unwrap_or(false)
7655
}
7756

78-
async fn reconcile(
79-
&self,
80-
ctx: Arc<Context>,
81-
pg_client: &mut tokio_postgres::Client,
82-
) -> Result<Action> {
57+
async fn reconcile(&self, ctx: Arc<Context>, pg: &mut Postgres) -> Result<Action, Error> {
8358
let client = ctx.client.clone();
8459
let ns = self.namespace().unwrap();
8560
let name = self.name_any();
@@ -89,9 +64,9 @@ impl DbSyncPort {
8964
let password = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
9065

9166
if !self.was_executed() {
92-
match user_already_exists(pg_client, &username).await? {
93-
true => user_enable(pg_client, &username, &password).await?,
94-
false => user_create(pg_client, &username, &password).await?,
67+
match pg.user_already_exists(&username).await? {
68+
true => pg.user_enable(&username, &password).await?,
69+
false => pg.user_create(&username, &password).await?,
9570
};
9671

9772
let new_status = Patch::Apply(json!({
@@ -114,19 +89,15 @@ impl DbSyncPort {
11489
Ok(Action::requeue(Duration::from_secs(5 * 60)))
11590
}
11691

117-
async fn cleanup(
118-
&self,
119-
ctx: Arc<Context>,
120-
pg_client: &mut tokio_postgres::Client,
121-
) -> Result<Action> {
92+
async fn cleanup(&self, ctx: Arc<Context>, pg: &mut Postgres) -> Result<Action, Error> {
12293
let username = self.status.as_ref().unwrap().username.clone();
123-
user_disable(pg_client, &username).await?;
94+
pg.user_disable(&username).await?;
12495
ctx.metrics.count_user_deactivated(&username);
12596
Ok(Action::await_change())
12697
}
12798
}
12899

129-
async fn reconcile(crd: Arc<DbSyncPort>, ctx: Arc<Context>) -> Result<Action> {
100+
async fn reconcile(crd: Arc<DbSyncPort>, ctx: Arc<Context>) -> Result<Action, Error> {
130101
let url = match crd.spec.network {
131102
Network::Mainnet => &ctx.config.db_url_mainnet,
132103
Network::Preprod => &ctx.config.db_url_preprod,
@@ -136,12 +107,12 @@ async fn reconcile(crd: Arc<DbSyncPort>, ctx: Arc<Context>) -> Result<Action> {
136107
let ns = crd.namespace().unwrap();
137108
let crds: Api<DbSyncPort> = Api::namespaced(ctx.client.clone(), &ns);
138109

139-
let mut pg_client = postgres::connect(url).await?;
110+
let mut postgres = Postgres::new(url).await?;
140111

141112
finalizer(&crds, DB_SYNC_PORT_FINALIZER, crd, |event| async {
142113
match event {
143-
Event::Apply(crd) => crd.reconcile(ctx.clone(), &mut pg_client).await,
144-
Event::Cleanup(crd) => crd.cleanup(ctx.clone(), &mut pg_client).await,
114+
Event::Apply(crd) => crd.reconcile(ctx.clone(), &mut postgres).await,
115+
Event::Cleanup(crd) => crd.cleanup(ctx.clone(), &mut postgres).await,
145116
}
146117
})
147118
.await
@@ -154,11 +125,11 @@ fn error_policy(crd: Arc<DbSyncPort>, err: &Error, ctx: Arc<Context>) -> Action
154125
Action::requeue(Duration::from_secs(5))
155126
}
156127

157-
pub async fn run(state: State, config: Config) -> Result<(), Error> {
128+
pub async fn run(state: Arc<State>, config: Config) -> Result<(), Error> {
158129
let client = Client::try_default().await?;
159130
let crds = Api::<DbSyncPort>::all(client.clone());
160-
let metrics = Metrics::default().register(&state.registry).unwrap();
161-
let ctx = Context::new(client, metrics, config);
131+
132+
let ctx = Context::new(client, state.metrics.clone(), config);
162133

163134
Controller::new(crds, WatcherConfig::default().any_semantic())
164135
.shutdown_on_signal()

src/crdgen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use ext_cardano_dbsync::controller;
12
use kube::CustomResourceExt;
23

34
fn main() {

src/lib.rs

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
use prometheus::Registry;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
14
use thiserror::Error;
25

6+
use std::{fmt::Display, time::Duration};
7+
38
#[derive(Error, Debug)]
49
pub enum Error {
510
#[error("Postgres Error: {0}")]
@@ -10,51 +15,131 @@ pub enum Error {
1015

1116
#[error("Finalizer Error: {0}")]
1217
FinalizerError(#[source] Box<kube::runtime::finalizer::Error<Error>>),
18+
19+
#[error("Env Error: {0}")]
20+
EnvError(#[source] std::env::VarError),
21+
22+
#[error("Prometheus Error: {0}")]
23+
PrometheusError(#[source] prometheus::Error),
24+
25+
#[error("Parse Int Error: {0}")]
26+
ParseIntError(#[source] std::num::ParseIntError),
27+
28+
#[error("Parse Float Error: {0}")]
29+
ParseFloatError(#[source] std::num::ParseFloatError),
1330
}
1431

1532
impl Error {
1633
pub fn metric_label(&self) -> String {
1734
format!("{self:?}").to_lowercase()
1835
}
1936
}
20-
21-
pub type Result<T, E = Error> = std::result::Result<T, E>;
22-
2337
impl From<tokio_postgres::Error> for Error {
2438
fn from(value: tokio_postgres::Error) -> Self {
2539
Error::PgError(value)
2640
}
2741
}
28-
2942
impl From<kube::Error> for Error {
3043
fn from(value: kube::Error) -> Self {
3144
Error::KubeError(value)
3245
}
3346
}
47+
impl From<std::env::VarError> for Error {
48+
fn from(value: std::env::VarError) -> Self {
49+
Error::EnvError(value)
50+
}
51+
}
52+
impl From<prometheus::Error> for Error {
53+
fn from(value: prometheus::Error) -> Self {
54+
Error::PrometheusError(value)
55+
}
56+
}
57+
impl From<std::num::ParseIntError> for Error {
58+
fn from(value: std::num::ParseIntError) -> Self {
59+
Error::ParseIntError(value)
60+
}
61+
}
62+
impl From<std::num::ParseFloatError> for Error {
63+
fn from(value: std::num::ParseFloatError) -> Self {
64+
Error::ParseFloatError(value)
65+
}
66+
}
3467

68+
#[derive(Clone, Default)]
69+
pub struct State {
70+
registry: Registry,
71+
pub metrics: Metrics,
72+
}
73+
impl State {
74+
pub fn new() -> Self {
75+
let registry = Registry::default();
76+
let metrics = Metrics::default().register(&registry).unwrap();
77+
Self { registry, metrics }
78+
}
79+
80+
pub fn metrics_collected(&self) -> Vec<prometheus::proto::MetricFamily> {
81+
self.registry.gather()
82+
}
83+
}
84+
85+
#[derive(Clone)]
3586
pub struct Config {
3687
pub db_url_mainnet: String,
3788
pub db_url_preprod: String,
3889
pub db_url_preview: String,
90+
91+
pub dcu_per_second_mainnet: f64,
92+
pub dcu_per_second_preprod: f64,
93+
pub dcu_per_second_preview: f64,
94+
95+
pub metrics_delay: Duration,
3996
}
4097
impl Config {
41-
pub fn new() -> Self {
42-
Self {
43-
db_url_mainnet: std::env::var("DB_URL_MAINNET").expect("DB_URL_MAINNET must be set"),
44-
db_url_preprod: std::env::var("DB_URL_PREPROD").expect("DB_URL_PREPROD must be set"),
45-
db_url_preview: std::env::var("DB_URL_PREVIEW").expect("DB_URL_PREVIEW must be set"),
46-
}
98+
pub fn try_new() -> Result<Self, Error> {
99+
let db_url_mainnet = std::env::var("DB_URL_MAINNET")?;
100+
let db_url_preprod = std::env::var("DB_URL_PREPROD")?;
101+
let db_url_preview = std::env::var("DB_URL_PREVIEW")?;
102+
103+
let metrics_delay = Duration::from_secs(std::env::var("METRICS_DELAY")?.parse::<u64>()?);
104+
105+
let dcu_per_second_mainnet = std::env::var("DCU_PER_SECOND_MAINNET")?.parse::<f64>()?;
106+
let dcu_per_second_preprod = std::env::var("DCU_PER_SECOND_PREPROD")?.parse::<f64>()?;
107+
let dcu_per_second_preview = std::env::var("DCU_PER_SECOND_PREVIEW")?.parse::<f64>()?;
108+
109+
Ok(Self {
110+
db_url_mainnet,
111+
db_url_preprod,
112+
db_url_preview,
113+
metrics_delay,
114+
dcu_per_second_mainnet,
115+
dcu_per_second_preprod,
116+
dcu_per_second_preview,
117+
})
47118
}
48119
}
49-
impl Default for Config {
50-
fn default() -> Self {
51-
Self::new()
120+
121+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
122+
pub enum Network {
123+
#[serde(rename = "mainnet")]
124+
Mainnet,
125+
#[serde(rename = "preprod")]
126+
Preprod,
127+
#[serde(rename = "preview")]
128+
Preview,
129+
}
130+
impl Display for Network {
131+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132+
match self {
133+
Network::Mainnet => write!(f, "mainnet"),
134+
Network::Preprod => write!(f, "preprod"),
135+
Network::Preview => write!(f, "preview"),
136+
}
52137
}
53138
}
54139

55140
pub mod controller;
141+
pub mod metrics;
56142
pub mod postgres;
57-
pub use crate::controller::*;
58143

59-
mod metrics;
60-
pub use metrics::Metrics;
144+
pub use controller::*;
145+
pub use metrics::*;

0 commit comments

Comments
 (0)