commit
ce7789b245
17 changed files with 953 additions and 0 deletions
@ -0,0 +1,53 @@ |
|||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information |
||||||
|
*.pdb### Rust template |
||||||
|
# Generated by Cargo |
||||||
|
# will have compiled files and executables |
||||||
|
debug/ |
||||||
|
release/ |
||||||
|
target/ |
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries |
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html |
||||||
|
Cargo.lock |
||||||
|
|
||||||
|
# These are backup files generated by rustfmt |
||||||
|
**/*.rs.bk |
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information |
||||||
|
*.pdb |
||||||
|
*.idea |
||||||
|
|
||||||
|
.idea/ |
||||||
|
|
||||||
|
### macOS template |
||||||
|
# General |
||||||
|
.DS_Store |
||||||
|
.AppleDouble |
||||||
|
.LSOverride |
||||||
|
|
||||||
|
# Icon must end with two |
||||||
|
Icon |
||||||
|
|
||||||
|
# Thumbnails |
||||||
|
._* |
||||||
|
|
||||||
|
# Files that might appear in the root of a volume |
||||||
|
.DocumentRevisions-V100 |
||||||
|
.fseventsd |
||||||
|
.Spotlight-V100 |
||||||
|
.TemporaryItems |
||||||
|
.Trashes |
||||||
|
.VolumeIcon.icns |
||||||
|
.com.apple.timemachine.donotpresent |
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share |
||||||
|
.AppleDB |
||||||
|
.AppleDesktop |
||||||
|
Network Trash Folder |
||||||
|
Temporary Items |
||||||
|
.apdisk |
||||||
|
|
||||||
|
# Gerrit |
||||||
|
/codereview |
||||||
|
/buildSrc |
||||||
|
detekt.yml |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
[package] |
||||||
|
name = "device-common" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
publish = ["nexus","https://maven.anypoint.tv/repository/crate/index"] |
||||||
|
repository = "https://Ryan:HEG2XSI4fF88+zR2DkVuiEzNXRMvC9Bd077s7qMpyg@" |
||||||
|
authors = ["Ryan Bae <jh.bae@anypointmedia.com>"] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
serde = { version = "1.0.202", features = ["derive"] } |
||||||
|
serde_json = "1.0.117" |
||||||
|
bincode = "1.3.3" |
||||||
|
aws-config = { version = "1.5.0" } |
||||||
|
aws-sdk-kinesis = { version = "1.27.0", features = ["behavior-version-latest"] } |
||||||
|
aws-sdk-ssm = "1.39" |
||||||
|
aws-types="1.3" |
||||||
|
aws-credential-types = { version = "1.2.0", features = ["hardcoded-credentials"] } |
||||||
|
deadpool-postgres = "0.14" |
||||||
|
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"] } |
||||||
|
tracing = "0.1" |
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] } |
||||||
|
tracing-unwrap = "1.0" |
||||||
|
tracing-appender = "0.2.3" |
||||||
|
thiserror = "1.0" |
||||||
|
tokio = "1.38.0" |
||||||
|
lapin = "2.3" |
||||||
|
sysinfo = "0.30" |
||||||
|
prometheus = "0.13" |
||||||
|
dashmap = "6.0" |
||||||
|
toml = "0.8" |
||||||
|
|
||||||
|
[profile.dev] |
||||||
|
opt-level = 0 |
||||||
|
|
||||||
|
[profile.release] |
||||||
|
opt-level = 3 |
||||||
@ -0,0 +1,115 @@ |
|||||||
|
use std::sync::Arc; |
||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
use tracing_unwrap::ResultExt; |
||||||
|
use tokio::sync::OnceCell; |
||||||
|
use crate::constants; |
||||||
|
use crate::parameter_store::get_parameter; |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct Log { |
||||||
|
pub level: String, |
||||||
|
} |
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct Rabbitmq { |
||||||
|
pub mapper: RabbitmqDetail, |
||||||
|
pub orchestration: RabbitmqDetail |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct RabbitmqDetail { |
||||||
|
pub host: String, |
||||||
|
pub port: i64, |
||||||
|
pub username: String, |
||||||
|
pub password: String, |
||||||
|
pub exchange: String, |
||||||
|
pub queue: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct Database { |
||||||
|
pub host: String, |
||||||
|
pub port: i64, |
||||||
|
pub user: String, |
||||||
|
pub password: String, |
||||||
|
pub name: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct AwsKinesisStreamDetail { |
||||||
|
pub access_key: String, |
||||||
|
pub secret_key: String, |
||||||
|
pub region: String, |
||||||
|
pub name: String, |
||||||
|
pub partition_key: String, |
||||||
|
pub arn: String, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct AwsKinesisStream { |
||||||
|
pub node: AwsKinesisStreamDetail, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)] |
||||||
|
pub struct Config { |
||||||
|
pub database: Database, |
||||||
|
pub rabbitmq: Rabbitmq, |
||||||
|
pub kinesis_stream: AwsKinesisStream, |
||||||
|
} |
||||||
|
|
||||||
|
static CONFIG: OnceCell<Arc<Config>> = OnceCell::const_new(); |
||||||
|
|
||||||
|
async fn initialize_data() -> Arc<Config> { |
||||||
|
let config = Config { |
||||||
|
database: Database { |
||||||
|
host: get_parameter(constants::DB_HOST_KEY.to_string()).await, |
||||||
|
port: get_parameter( constants::DB_PORT_KEY.to_string()) |
||||||
|
.await |
||||||
|
.parse() |
||||||
|
.unwrap_or_log(), |
||||||
|
user: get_parameter(constants::DB_USER_KEY.to_string()).await, |
||||||
|
password: get_parameter(constants::DB_PASSWORD_KEY.to_string()).await, |
||||||
|
name: get_parameter(constants::DB_NAME_KEY.to_string()).await, |
||||||
|
}, |
||||||
|
rabbitmq: Rabbitmq { |
||||||
|
mapper: RabbitmqDetail { |
||||||
|
host: get_parameter(constants::RABBITMQ_HOST_KEY.to_string()).await, |
||||||
|
port: get_parameter(constants::RABBITMQ_PORT_KEY.to_string()) |
||||||
|
.await |
||||||
|
.parse() |
||||||
|
.unwrap_or_log(), |
||||||
|
username: get_parameter(constants::RABBITMQ_USER_KEY.to_string()).await, |
||||||
|
password: get_parameter(constants::RABBITMQ_PASSWORD_KEY.to_string()).await, |
||||||
|
exchange: get_parameter(constants::RABBITMQ_MAPPER_EXCHANGE_KEY.to_string()).await, |
||||||
|
queue: get_parameter(constants::RABBITMQ_MAPPER_QUEUE_KEY.to_string()).await, |
||||||
|
}, |
||||||
|
|
||||||
|
orchestration: RabbitmqDetail { |
||||||
|
host: get_parameter(constants::RABBITMQ_HOST_KEY.to_string()).await, |
||||||
|
port: get_parameter(constants::RABBITMQ_PORT_KEY.to_string()) |
||||||
|
.await |
||||||
|
.parse() |
||||||
|
.unwrap_or_log(), |
||||||
|
username: get_parameter(constants::RABBITMQ_USER_KEY.to_string()).await, |
||||||
|
password: get_parameter(constants::RABBITMQ_PASSWORD_KEY.to_string()).await, |
||||||
|
exchange: get_parameter(constants::RABBITMQ_ORCHESTRATION_EXCHANGE_KEY.to_string()).await, |
||||||
|
queue: get_parameter(constants::RABBITMQ_ORCHESTRATION_QUEUE_KEY.to_string()).await, |
||||||
|
}, |
||||||
|
}, |
||||||
|
kinesis_stream: AwsKinesisStream { |
||||||
|
node: AwsKinesisStreamDetail { |
||||||
|
access_key: crate::env::get(constants::ENV_AWS_ACCESS_KEY).expect_or_log("env aws_access_key not found"), |
||||||
|
secret_key: crate::env::get(constants::ENV_AWS_SECRET_KEY).expect_or_log("env aws_secret_key not found"), |
||||||
|
region: crate::env::get(constants::ENV_AWS_REGION).expect_or_log("env aws_region not found"), |
||||||
|
name: get_parameter(constants::KINESIS_PERSISTENCE_NAME_KEY.to_string()).await, |
||||||
|
partition_key: get_parameter(constants::KINESIS_PERSISTENCE_PARTITION_KEY.to_string()).await, |
||||||
|
arn: get_parameter(constants::KINESIS_PERSISTENCE_ARN_KEY.to_string()).await, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
Arc::new(config) |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn get_config() -> Arc<Config> { |
||||||
|
CONFIG.get_or_init(initialize_data).await.clone() |
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
pub const GATEWAY_LOG_LEVEL_KEY: &str = "/flower/device/gateway/log/level"; |
||||||
|
pub const NODE_LOG_LEVEL_KEY: &str = "/flower/device/node/log/level"; |
||||||
|
pub const TICKET_LOG_LEVEL_KEY: &str = "/flower/device/ticket/log/level"; |
||||||
|
pub const FAILOVER_LOG_LEVEL_KEY: &str = "/flower/device/failover/log/level"; |
||||||
|
|
||||||
|
pub const RABBITMQ_HOST_KEY: &str = "/flower/device/common/rabbitmq/host"; |
||||||
|
pub const RABBITMQ_PORT_KEY: &str = "/flower/device/common/rabbitmq/port"; |
||||||
|
pub const RABBITMQ_USER_KEY: &str = "/flower/device/common/rabbitmq/user"; |
||||||
|
pub const RABBITMQ_PASSWORD_KEY: &str = "/flower/device/common/rabbitmq/password"; |
||||||
|
pub const RABBITMQ_MAPPER_EXCHANGE_KEY: &str = "/flower/device/mapper/rabbitmq/exchange"; |
||||||
|
pub const RABBITMQ_MAPPER_QUEUE_KEY: &str = "/flower/device/mapper/rabbitmq/queue"; |
||||||
|
pub const RABBITMQ_ORCHESTRATION_EXCHANGE_KEY: &str = "/flower/device/orchestration/rabbitmq/exchange"; |
||||||
|
pub const RABBITMQ_ORCHESTRATION_QUEUE_KEY: &str = "/flower/device/orchestration/rabbitmq/queue"; |
||||||
|
|
||||||
|
pub const KINESIS_PERSISTENCE_NAME_KEY: &str = "/flower/device/persistence/kinesis/name"; |
||||||
|
pub const KINESIS_PERSISTENCE_PARTITION_KEY: &str = "/flower/device/persistence/kinesis/partition_key"; |
||||||
|
pub const KINESIS_PERSISTENCE_ARN_KEY: &str = "/flower/device/persistence/kinesis/arn"; |
||||||
|
|
||||||
|
pub const KINESIS_FAILOVER_NAME_KEY: &str = "/flower/device/failover/kinesis/name"; |
||||||
|
pub const KINESIS_FAILOVER_PARTITION_KEY: &str = "/flower/device/failover/kinesis/partition_key"; |
||||||
|
pub const KINESIS_FAILOVER_ARN_KEY: &str = "/flower/device/failover/kinesis/arn"; |
||||||
|
|
||||||
|
pub const DB_HOST_KEY: &str = "/flower/device/common/db/host"; |
||||||
|
pub const DB_PORT_KEY: &str = "/flower/device/common/db/port"; |
||||||
|
pub const DB_USER_KEY: &str = "/flower/device/common/db/user"; |
||||||
|
pub const DB_PASSWORD_KEY: &str = "/flower/device/common/db/password"; |
||||||
|
pub const DB_NAME_KEY: &str = "/flower/device/common/db/name"; |
||||||
|
|
||||||
|
pub const ENV_AWS_ACCESS_KEY: &str = "AWS_ACCESS_KEY"; |
||||||
|
pub const ENV_AWS_SECRET_KEY: &str = "AWS_SECRET_KEY"; |
||||||
|
pub const ENV_AWS_REGION: &str = "AWS_REGION"; |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
use std::sync::Arc; |
||||||
|
use deadpool_postgres::{Pool, Config, Runtime}; |
||||||
|
use tokio::sync::OnceCell; |
||||||
|
use tokio_postgres::NoTls; |
||||||
|
use crate::config::get_config; |
||||||
|
|
||||||
|
static CONNECTION_POOL: OnceCell<Arc<Pool>> = OnceCell::const_new(); |
||||||
|
|
||||||
|
async fn initialize_data() -> Arc<Pool> { |
||||||
|
let config = get_config().await; |
||||||
|
|
||||||
|
let mut pg = Config::new(); |
||||||
|
pg.host = Some(config.database.host.clone()); |
||||||
|
pg.user = Some(config.database.user.clone()); |
||||||
|
pg.password = Some(config.database.password.clone()); |
||||||
|
pg.dbname = Some(config.database.name.clone()); |
||||||
|
pg.port = Some(config.database.port as u16); |
||||||
|
|
||||||
|
let pool = pg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap(); |
||||||
|
|
||||||
|
Arc::new(pool) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
pub async fn initialize_data() -> Arc<Pool> { |
||||||
|
let config = get_config().await; |
||||||
|
|
||||||
|
let mut current_dir = current_exe().unwrap(); |
||||||
|
current_dir.pop(); |
||||||
|
current_dir.push("db.pem"); |
||||||
|
let cert_file = File::open(current_dir.display().to_string()).unwrap(); |
||||||
|
let mut buf = BufReader::new(cert_file); |
||||||
|
let mut root_store = rustls::RootCertStore::empty(); |
||||||
|
for cert in rustls_pemfile::certs(&mut buf) { |
||||||
|
root_store.add(cert.unwrap()).unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
let tls_config = rustls::ClientConfig::builder() |
||||||
|
.with_root_certificates(root_store) |
||||||
|
.with_no_client_auth(); |
||||||
|
|
||||||
|
let tls = MakeRustlsConnect::new(tls_config); |
||||||
|
|
||||||
|
let mut pg = deadpool_postgres::Config::new(); |
||||||
|
pg.host = Some(config.database.host.clone()); |
||||||
|
pg.user = Some(config.database.user.clone()); |
||||||
|
pg.password = Some(config.database.password.clone()); |
||||||
|
pg.dbname = Some(config.database.name.clone()); |
||||||
|
pg.port = Some(config.database.port as u16); |
||||||
|
|
||||||
|
let pool = pg.create_pool(Some(Runtime::Tokio1), tls).unwrap(); |
||||||
|
|
||||||
|
Arc::new(pool) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
/* |
||||||
|
pub async fn initialize_data() -> Arc<Pool> { |
||||||
|
let config = get_config().await; |
||||||
|
|
||||||
|
let mut current_dir = current_exe().unwrap(); |
||||||
|
current_dir.pop(); |
||||||
|
current_dir.push("db.pem"); |
||||||
|
let cert_file = File::open(current_dir.display().to_string()).unwrap(); |
||||||
|
let mut buf = BufReader::new(cert_file); |
||||||
|
let mut root_store = rustls::RootCertStore::empty(); |
||||||
|
for cert in rustls_pemfile::certs(&mut buf) { |
||||||
|
root_store.add(cert.unwrap()).unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
let tls_config = TlsConnector::builder() |
||||||
|
.danger_accept_invalid_certs(true) |
||||||
|
.build() |
||||||
|
.expect_or_log(""); |
||||||
|
|
||||||
|
let tls = MakeTlsConnector::new(tls_config); |
||||||
|
|
||||||
|
let mut pg = deadpool_postgres::Config::new(); |
||||||
|
pg.host = Some(config.database.host.clone()); |
||||||
|
pg.user = Some(config.database.user.clone()); |
||||||
|
pg.password = Some(config.database.password.clone()); |
||||||
|
pg.dbname = Some(config.database.name.clone()); |
||||||
|
pg.port = Some(config.database.port as u16); |
||||||
|
|
||||||
|
let pool = pg.create_pool(Some(Runtime::Tokio1), tls).unwrap(); |
||||||
|
|
||||||
|
Arc::new(pool) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
pub async fn get_connection_pool() -> Arc<Pool> { |
||||||
|
CONNECTION_POOL.get_or_init(initialize_data) |
||||||
|
.await |
||||||
|
.clone() |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
use std::env; |
||||||
|
use std::error::Error; |
||||||
|
use std::str::FromStr; |
||||||
|
|
||||||
|
pub fn set(key: &str, val: &str) { |
||||||
|
env::set_var(key, val); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get(key: &str) -> Result<String, Box<dyn Error>> { |
||||||
|
let ret = env::var(key)?; |
||||||
|
Ok(ret) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_i64(key: &str) -> Result<i64, Box<dyn Error>> { |
||||||
|
let str_env = env::var(key)?; |
||||||
|
let ret = i64::from_str(&str_env)?; |
||||||
|
Ok(ret) |
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
use std::sync::Arc; |
||||||
|
use aws_config::Region; |
||||||
|
use aws_sdk_kinesis::{Client, Config}; |
||||||
|
use aws_sdk_kinesis::primitives::Blob; |
||||||
|
use aws_credential_types::Credentials; |
||||||
|
use tokio::sync::OnceCell; |
||||||
|
use crate::model::error::DeviceError; |
||||||
|
use crate::model::kinesis_message::KinesisMessage; |
||||||
|
|
||||||
|
static AWS_KINESIS_CONSUMER_CLIENT: OnceCell<Arc<Client>> = OnceCell::const_new(); |
||||||
|
|
||||||
|
async fn initialize_data() -> Arc<Client> { |
||||||
|
let config = crate::config::get_config() |
||||||
|
.await; |
||||||
|
|
||||||
|
let access_key_id = &config.kinesis_stream.node.access_key.clone(); |
||||||
|
let secret_access_key = &config.kinesis_stream.node.secret_key.clone(); |
||||||
|
let region = &config.kinesis_stream.node.region.clone(); |
||||||
|
|
||||||
|
let credentials = Credentials::from_keys(access_key_id, secret_access_key, None); |
||||||
|
let aws_config = Config::builder() |
||||||
|
.credentials_provider(credentials) |
||||||
|
.region(Region::new(region.clone())) |
||||||
|
.build(); |
||||||
|
|
||||||
|
Arc::new(Client::from_conf(aws_config)) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_client() -> Arc<Client> { |
||||||
|
AWS_KINESIS_CONSUMER_CLIENT.get_or_init(initialize_data) |
||||||
|
.await |
||||||
|
.clone() |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn kinesis_put_record(msg: &KinesisMessage) -> Result<(), DeviceError> { |
||||||
|
let blob = msg.encode(); |
||||||
|
let config = crate::config::get_config() |
||||||
|
.await |
||||||
|
.clone(); |
||||||
|
|
||||||
|
get_client() |
||||||
|
.await |
||||||
|
.put_record() |
||||||
|
.data(Blob::new(blob)) |
||||||
|
.partition_key(&config.kinesis_stream.node.partition_key) |
||||||
|
.stream_name(&config.kinesis_stream.node.name) |
||||||
|
.stream_arn(&config.kinesis_stream.node.arn) |
||||||
|
.send() |
||||||
|
.await?; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
|
||||||
|
pub mod config; |
||||||
|
pub mod model; |
||||||
|
pub mod database; |
||||||
|
pub mod kinesis; |
||||||
|
pub mod log; |
||||||
|
pub mod env; |
||||||
|
pub mod orchestration; |
||||||
|
pub mod metrics; |
||||||
|
pub mod parameter_store; |
||||||
|
pub mod constants; |
||||||
@ -0,0 +1,98 @@ |
|||||||
|
use std::env::current_exe; |
||||||
|
use std::fs; |
||||||
|
use tracing::{Level, subscriber}; |
||||||
|
use tracing_appender::non_blocking::{WorkerGuard}; |
||||||
|
use tracing_appender::rolling::{RollingFileAppender, Rotation}; |
||||||
|
use tracing_unwrap::{OptionExt, ResultExt}; |
||||||
|
use crate::constants::{FAILOVER_LOG_LEVEL_KEY, GATEWAY_LOG_LEVEL_KEY, NODE_LOG_LEVEL_KEY, TICKET_LOG_LEVEL_KEY}; |
||||||
|
use crate::parameter_store::get_parameter; |
||||||
|
|
||||||
|
fn read_cargo_toml() -> toml::Value{ |
||||||
|
let cargo_toml_content = fs::read_to_string("Cargo.toml") |
||||||
|
.expect_or_log("Failed to read Cargo.toml file"); |
||||||
|
|
||||||
|
let cargo_toml: toml::Value = toml::from_str(&cargo_toml_content) |
||||||
|
.expect_or_log("Failed to parse Cargo.toml"); |
||||||
|
|
||||||
|
cargo_toml |
||||||
|
} |
||||||
|
|
||||||
|
fn get_app_name() -> Option<String> { |
||||||
|
let cargo_toml = read_cargo_toml(); |
||||||
|
|
||||||
|
let package = cargo_toml |
||||||
|
.get("package") |
||||||
|
.expect("`[package]` section is missing in Cargo.toml"); |
||||||
|
|
||||||
|
let name = package |
||||||
|
.get("name") |
||||||
|
.expect_or_log("`name` field is missing in Cargo.toml"); |
||||||
|
|
||||||
|
let name_str = name |
||||||
|
.as_str() |
||||||
|
.expect_or_log("Package name is not a string"); |
||||||
|
|
||||||
|
Option::from(name_str.to_string()) |
||||||
|
} |
||||||
|
|
||||||
|
fn get_log_level_parameter_key() -> Option<String> { |
||||||
|
let mut ret: Option<String> = None; |
||||||
|
|
||||||
|
match get_app_name() { |
||||||
|
None => { ret = None } |
||||||
|
Some(app_name) => { |
||||||
|
match app_name.as_str() { |
||||||
|
"device-gateway" => { ret = Option::from(GATEWAY_LOG_LEVEL_KEY.to_string()) } |
||||||
|
"device-node" => { ret = Option::from(NODE_LOG_LEVEL_KEY.to_string()) } |
||||||
|
"device-ticket" => { ret = Option::from(TICKET_LOG_LEVEL_KEY.to_string()) } |
||||||
|
"device-failover" => { ret = Option::from(FAILOVER_LOG_LEVEL_KEY.to_string()) } |
||||||
|
_ => { return ret } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ret |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn init(log_file_name: &str) -> WorkerGuard { |
||||||
|
let mut level: Level = Level::INFO; |
||||||
|
|
||||||
|
let log_level_parameter_key = get_log_level_parameter_key() |
||||||
|
.expect_or_log("log level parameter key not found"); |
||||||
|
|
||||||
|
let log_level = get_parameter(log_level_parameter_key).await; |
||||||
|
|
||||||
|
match log_level.to_lowercase().as_str() { |
||||||
|
"debug" => { level = Level::DEBUG }, |
||||||
|
"info" => { level = Level::INFO }, |
||||||
|
"warn" => { level = Level::WARN }, |
||||||
|
_ => {} |
||||||
|
} |
||||||
|
|
||||||
|
let file_appender = RollingFileAppender::builder() |
||||||
|
.rotation(Rotation::DAILY) |
||||||
|
.filename_suffix(log_file_name) |
||||||
|
.build(get_log_dir()) |
||||||
|
.expect_or_log("failed to initialize rolling file appender"); |
||||||
|
|
||||||
|
let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); |
||||||
|
|
||||||
|
let subscriber = tracing_subscriber::fmt() |
||||||
|
.with_max_level(level) |
||||||
|
.with_writer(file_writer) |
||||||
|
//.with_writer(console_writer)
|
||||||
|
.with_ansi(false) |
||||||
|
.finish(); |
||||||
|
|
||||||
|
subscriber::set_global_default(subscriber) |
||||||
|
.expect_or_log("trace init fail"); |
||||||
|
|
||||||
|
_guard |
||||||
|
} |
||||||
|
|
||||||
|
fn get_log_dir() -> String { |
||||||
|
let mut current_dir = current_exe().unwrap(); |
||||||
|
current_dir.pop(); |
||||||
|
current_dir.push("log"); |
||||||
|
current_dir.display().to_string() |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
use sysinfo::System; |
||||||
|
use prometheus::{register_gauge, Gauge}; |
||||||
|
use std::sync::OnceLock; |
||||||
|
|
||||||
|
pub fn get_cpu_usage() -> &'static Gauge { |
||||||
|
static GAUGE: OnceLock<Gauge> = OnceLock::new(); |
||||||
|
GAUGE.get_or_init(|| { |
||||||
|
register_gauge!("cpu_usage", "CPU Usage").unwrap() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_memory_used() -> &'static Gauge { |
||||||
|
static GAUGE: OnceLock<Gauge> = OnceLock::new(); |
||||||
|
GAUGE.get_or_init(|| { |
||||||
|
register_gauge!("memory_used", "Memory Used").unwrap() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_memory_total() -> &'static Gauge { |
||||||
|
static GAUGE: OnceLock<Gauge> = OnceLock::new(); |
||||||
|
GAUGE.get_or_init(|| { |
||||||
|
register_gauge!("memory_total", "Memory Total").unwrap() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_memory_usage() -> &'static Gauge { |
||||||
|
static GAUGE: OnceLock<Gauge> = OnceLock::new(); |
||||||
|
GAUGE.get_or_init(|| { |
||||||
|
register_gauge!("memory_usage", "Memory Usage").unwrap() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn collect(system: &mut System) -> (f64, f64, f64, f64) { |
||||||
|
system.refresh_all(); |
||||||
|
|
||||||
|
let cpu_usage = system.global_cpu_info().cpu_usage() as f64; |
||||||
|
let memory_used = system.used_memory() as f64; |
||||||
|
let memory_total = system.total_memory() as f64; |
||||||
|
let memory_usage = memory_used / memory_total * 100.0; |
||||||
|
|
||||||
|
(cpu_usage, memory_used, memory_total, memory_usage) |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
use aws_sdk_kinesis::operation::put_record::PutRecordError; |
||||||
|
use aws_sdk_ssm::operation::get_parameter::GetParameterError; |
||||||
|
use thiserror::Error; |
||||||
|
use tokio_postgres::Error as PostgresError; |
||||||
|
|
||||||
|
#[derive(Error, Debug)] |
||||||
|
pub enum DeviceError { |
||||||
|
#[error("flower-device-app / An error occurred: {0}")] |
||||||
|
GeneralError(String), |
||||||
|
|
||||||
|
#[error("I/O error")] |
||||||
|
IoError(#[from] std::io::Error), |
||||||
|
|
||||||
|
#[error("VarError")] |
||||||
|
VarError(#[from] std::env::VarError), |
||||||
|
|
||||||
|
#[error("Parse error: {0}")] |
||||||
|
ParseError(#[from] std::num::ParseIntError), |
||||||
|
|
||||||
|
#[error("Database error: {0}")] |
||||||
|
DatabaseError(#[from] PostgresError), |
||||||
|
|
||||||
|
#[error("RabbitMq error: {0}")] |
||||||
|
RabbitMqError(#[from] lapin::Error), |
||||||
|
|
||||||
|
#[error("Kinesis Stream error: {0}")] |
||||||
|
PutRecordError(#[from] aws_sdk_kinesis::error::SdkError<PutRecordError>), |
||||||
|
|
||||||
|
#[error("Parameter Store error: {0}")] |
||||||
|
GetParameterError(#[from] aws_sdk_ssm::error::SdkError<GetParameterError>), |
||||||
|
|
||||||
|
#[error("serde_json error: {0}")] |
||||||
|
SerdeJsonError(#[from] serde_json::error::Error) |
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
use std::mem::size_of; |
||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
|
||||||
|
#[repr(u8)] |
||||||
|
#[derive(Debug, PartialEq)] |
||||||
|
pub enum KinesisMessageType { |
||||||
|
DeviceUpsert = 0x01 |
||||||
|
} |
||||||
|
|
||||||
|
#[repr(C)] |
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)] |
||||||
|
pub struct KinesisMessage { |
||||||
|
pub action: u8, // data type
|
||||||
|
pub size: i32, // data size
|
||||||
|
pub data: Vec<u8> // data
|
||||||
|
} |
||||||
|
|
||||||
|
impl KinesisMessage { |
||||||
|
pub fn new(action: u8, size: i32, data: Vec<u8>) -> KinesisMessage { |
||||||
|
KinesisMessage { |
||||||
|
action, |
||||||
|
size, |
||||||
|
data, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn encode(&self) -> Vec<u8> { |
||||||
|
let mut bin = Vec::new(); |
||||||
|
bin.push(self.action); |
||||||
|
bin.extend_from_slice(&self.size.to_ne_bytes()); |
||||||
|
bin.extend_from_slice(&self.data); |
||||||
|
bin |
||||||
|
} |
||||||
|
|
||||||
|
pub fn decode(encoded: Vec<u8>) -> KinesisMessage { |
||||||
|
let mut current_index = 0; |
||||||
|
let action = encoded[current_index]; |
||||||
|
current_index = current_index + size_of::<u8>(); |
||||||
|
|
||||||
|
let size_vec = encoded.get(current_index..current_index + size_of::<i32>()).unwrap().try_into().unwrap(); |
||||||
|
let size = i32::from_ne_bytes(size_vec); |
||||||
|
current_index = current_index + size_of::<i32>(); |
||||||
|
|
||||||
|
let data = encoded[current_index..current_index + size as usize].to_vec(); |
||||||
|
|
||||||
|
KinesisMessage { |
||||||
|
action, |
||||||
|
size, |
||||||
|
data, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
|
||||||
|
pub trait KinesisMessageDetail<T> { |
||||||
|
fn encode(&self) -> Vec<u8>; |
||||||
|
fn decode(encoded: Vec<u8>) -> T; |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)] |
||||||
|
#[repr(C)] |
||||||
|
pub struct UpsertKinesisMessageDetail { |
||||||
|
pub finger_print: String, |
||||||
|
pub so_id: String, |
||||||
|
pub uuid: String, |
||||||
|
pub use_personalized_ad: bool, |
||||||
|
pub test: Option<bool>, |
||||||
|
pub zip_code: Option<String>, |
||||||
|
pub free_storage: i64, |
||||||
|
pub used_storage: i64, |
||||||
|
pub cached_storage: i64, |
||||||
|
pub model_name: String, |
||||||
|
pub firmware_build_date: Option<String>, |
||||||
|
pub firmware_ver: Option<String>, |
||||||
|
pub full_firmware_ver: Option<String>, |
||||||
|
pub app_version: String, |
||||||
|
pub platform_ad_id: Option<String>, |
||||||
|
pub a_key: Option<String>, |
||||||
|
pub is_exist: bool, |
||||||
|
pub device_id: i64 |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> KinesisMessageDetail<T> for UpsertKinesisMessageDetail |
||||||
|
where |
||||||
|
T: Serialize + serde::de::DeserializeOwned,{ |
||||||
|
fn encode(&self) -> Vec<u8> { |
||||||
|
bincode::serialize(&self).unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
fn decode(encoded: Vec<u8>) -> T { |
||||||
|
bincode::deserialize(&encoded).unwrap() |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
pub mod kinesis_message_detail; |
||||||
|
pub mod kinesis_message; |
||||||
|
pub mod error; |
||||||
|
pub mod orchestration; |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||||
|
pub struct OrchestrationConfig { |
||||||
|
pub quotient: i64, |
||||||
|
pub shard_count: i32, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||||
|
pub struct OrchestrationNodeInfo { |
||||||
|
pub id: i64, |
||||||
|
pub platform_id: i32, |
||||||
|
pub remains: Vec<i64>, |
||||||
|
pub endpoint: String, |
||||||
|
pub status: i32 |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||||
|
pub struct OrchestrationGatewayInfo { |
||||||
|
pub id: i64, |
||||||
|
pub endpoint: String, |
||||||
|
pub ticket_endpoint: String, |
||||||
|
} |
||||||
@ -0,0 +1,157 @@ |
|||||||
|
use std::env; |
||||||
|
use std::str::FromStr; |
||||||
|
use std::sync::{Arc, Mutex, OnceLock}; |
||||||
|
use dashmap::DashMap; |
||||||
|
use tracing::info; |
||||||
|
use crate::model::error::DeviceError; |
||||||
|
use crate::model::orchestration::{OrchestrationConfig, OrchestrationGatewayInfo, OrchestrationNodeInfo}; |
||||||
|
|
||||||
|
pub fn get_orchestration_config() -> &'static Arc<Mutex<OrchestrationConfig>> { |
||||||
|
static ORCHESTRATION_CONFIG: OnceLock<Arc<Mutex<OrchestrationConfig>>> = OnceLock::new(); |
||||||
|
ORCHESTRATION_CONFIG.get_or_init(|| Arc::new(Mutex::new(OrchestrationConfig { |
||||||
|
quotient: 0, |
||||||
|
shard_count: 0, |
||||||
|
}))) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_orchestration_node_info() -> &'static Arc<DashMap<i64, OrchestrationNodeInfo>> { |
||||||
|
static ORCHESTRATION_NODE_INFO: OnceLock<Arc<DashMap<i64, OrchestrationNodeInfo>>>= OnceLock::new(); |
||||||
|
ORCHESTRATION_NODE_INFO.get_or_init(|| Arc::new(DashMap::new())) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_orchestration_gateway_info() -> &'static Arc<DashMap<i64, OrchestrationGatewayInfo>> { |
||||||
|
static ORCHESTRATION_GATEWAY_INFO: OnceLock<Arc<DashMap<i64, OrchestrationGatewayInfo>>> = OnceLock::new(); |
||||||
|
ORCHESTRATION_GATEWAY_INFO.get_or_init(|| Arc::new(DashMap::new())) |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn load_orchestration_config() -> Result<(), DeviceError> { |
||||||
|
let pool = crate::database::get_connection_pool().await; |
||||||
|
let client = pool.get().await.unwrap(); |
||||||
|
let fetch_query = "SELECT * FROM orchestration_config"; |
||||||
|
|
||||||
|
let result = client |
||||||
|
.query(fetch_query, &[]) |
||||||
|
.await?; |
||||||
|
|
||||||
|
match result.get(0) { |
||||||
|
None => { |
||||||
|
return Err(DeviceError::GeneralError(String::from("empty orchestration config"))) |
||||||
|
} |
||||||
|
Some(v) => { |
||||||
|
let mut config = get_orchestration_config().lock().unwrap(); |
||||||
|
config.quotient = v.get("quotient"); |
||||||
|
config.shard_count = v.get("shard_count"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn load_orchestration_node_info() -> Result<(), DeviceError> { |
||||||
|
let pool = crate::database::get_connection_pool().await; |
||||||
|
let client = pool.get().await.unwrap(); |
||||||
|
let fetch_query = "SELECT * FROM orchestration_node_info"; |
||||||
|
|
||||||
|
let result = client |
||||||
|
.query(fetch_query, &[]) |
||||||
|
.await?; |
||||||
|
|
||||||
|
let cache = get_orchestration_node_info(); |
||||||
|
cache.clear(); |
||||||
|
|
||||||
|
let mut count = 0; |
||||||
|
for row in result.into_iter() { |
||||||
|
let id: i64 = row.get("id"); |
||||||
|
|
||||||
|
let mut node_info = OrchestrationNodeInfo { |
||||||
|
id, |
||||||
|
platform_id: row.get("platform_id"), |
||||||
|
remains: vec![], |
||||||
|
endpoint: row.get("endpoint"), |
||||||
|
status: row.get("status"), |
||||||
|
}; |
||||||
|
|
||||||
|
let remain_str: String = row.get("remains"); |
||||||
|
let remains: Vec<&str> = remain_str.split(',').collect(); |
||||||
|
let int_vec = remains |
||||||
|
.iter() |
||||||
|
.map(|s| s.parse::<i64>().expect("parse error : invalid node id")) |
||||||
|
.collect(); |
||||||
|
node_info.remains = int_vec; |
||||||
|
|
||||||
|
cache.insert(id, node_info); |
||||||
|
count += 1; |
||||||
|
} |
||||||
|
|
||||||
|
info!("node_info loaded, count : {}", count); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn load_orchestration_gateway_info() -> Result<(), DeviceError> { |
||||||
|
let pool = crate::database::get_connection_pool().await; |
||||||
|
let client = pool.get().await.unwrap(); |
||||||
|
let fetch_query = "SELECT * FROM orchestration_gateway_info"; |
||||||
|
|
||||||
|
let result = client |
||||||
|
.query(fetch_query, &[]) |
||||||
|
.await?; |
||||||
|
|
||||||
|
let cache = get_orchestration_gateway_info(); |
||||||
|
cache.clear(); |
||||||
|
|
||||||
|
let mut count = 0; |
||||||
|
for row in result.into_iter() { |
||||||
|
let id: i64 = row.get("id"); |
||||||
|
|
||||||
|
let gateway_info = OrchestrationGatewayInfo { |
||||||
|
id, |
||||||
|
endpoint: row.get("endpoint"), |
||||||
|
ticket_endpoint: row.get("ticket_endpoint") |
||||||
|
}; |
||||||
|
|
||||||
|
cache.insert(id, gateway_info); |
||||||
|
count += 1; |
||||||
|
} |
||||||
|
|
||||||
|
info!("gateway_info loaded, count : {}", count); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_node_info(id: i64) -> Option<OrchestrationNodeInfo> { |
||||||
|
let node_cache = get_orchestration_node_info(); |
||||||
|
|
||||||
|
match node_cache.get_mut(&id) { |
||||||
|
None => { None } |
||||||
|
Some(node) => { |
||||||
|
Option::from(node.value().clone()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_node_info_by_device_id(device_id: i64) -> Option<OrchestrationNodeInfo> { |
||||||
|
let node_cache = get_orchestration_node_info(); |
||||||
|
|
||||||
|
for node in node_cache.iter() { |
||||||
|
let remain = get_orchestration_config() |
||||||
|
.lock() |
||||||
|
.unwrap() |
||||||
|
.quotient % device_id; |
||||||
|
|
||||||
|
match node.remains.iter().find(|&&x| x == remain) { |
||||||
|
Some(_) => { |
||||||
|
return Option::from(node.value().clone()) |
||||||
|
} |
||||||
|
_ => {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_current_app_id() -> Result<i64, DeviceError> { |
||||||
|
let id_str = env::var("ID")?; |
||||||
|
let id = i64::from_str(&id_str)?; |
||||||
|
Ok(id) |
||||||
|
} |
||||||
@ -0,0 +1,91 @@ |
|||||||
|
use std::sync::Arc; |
||||||
|
use aws_config::meta::region::RegionProviderChain; |
||||||
|
use aws_credential_types::Credentials; |
||||||
|
use aws_sdk_ssm::Client; |
||||||
|
use aws_types::region::Region; |
||||||
|
use tokio::sync::OnceCell; |
||||||
|
use tracing_unwrap::{OptionExt, ResultExt}; |
||||||
|
use crate::constants; |
||||||
|
|
||||||
|
static AWS_SSM_CLIENT: OnceCell<Arc<Client>> = OnceCell::const_new(); |
||||||
|
|
||||||
|
fn get_aws_credential() -> (String, String, String) { |
||||||
|
let access_key = crate::env::get(constants::ENV_AWS_ACCESS_KEY) |
||||||
|
.expect_or_log("env not found: AWS_ACCESS_KEY"); |
||||||
|
let secret_key = crate::env::get(constants::ENV_AWS_SECRET_KEY) |
||||||
|
.expect_or_log("env not found: AWS_SECRET_KEY"); |
||||||
|
let region = crate::env::get(constants::ENV_AWS_REGION) |
||||||
|
.expect_or_log("env not found: AWS_REGION"); |
||||||
|
|
||||||
|
(access_key, secret_key, region) |
||||||
|
} |
||||||
|
|
||||||
|
async fn initialize_data() -> Arc<Client> { |
||||||
|
let (access_key, secret_key, region) = get_aws_credential(); |
||||||
|
|
||||||
|
let region_provider = RegionProviderChain::default_provider().or_else(Region::new(region)); |
||||||
|
let region = region_provider.region().await.unwrap(); |
||||||
|
|
||||||
|
let credentials = Credentials::from_keys( |
||||||
|
access_key, |
||||||
|
secret_key, |
||||||
|
None, |
||||||
|
); |
||||||
|
|
||||||
|
let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) |
||||||
|
.region(region) |
||||||
|
.credentials_provider(credentials) |
||||||
|
.load() |
||||||
|
.await; |
||||||
|
|
||||||
|
Arc::new(Client::new(&config)) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_client() -> Arc<Client> { |
||||||
|
AWS_SSM_CLIENT.get_or_init(initialize_data) |
||||||
|
.await |
||||||
|
.clone() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_prefix() -> String { |
||||||
|
let mut profile: String = "".to_string(); |
||||||
|
let name = "RUST_PROFILE"; |
||||||
|
|
||||||
|
match std::env::var(name) { |
||||||
|
Ok(v) => profile = v, |
||||||
|
Err(e) => panic!("${} is not set ({})", name, e), |
||||||
|
} |
||||||
|
|
||||||
|
let mut prefix: String = "".to_string(); |
||||||
|
match profile.as_str() { |
||||||
|
"local" => { |
||||||
|
prefix = "/dev".to_string(); |
||||||
|
}, |
||||||
|
_ => { |
||||||
|
prefix = profile |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
prefix |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn get_parameter(key: String) -> String { |
||||||
|
let key_profile = get_prefix() + &key; |
||||||
|
let parameter_output = get_client() |
||||||
|
.await |
||||||
|
.get_parameter() |
||||||
|
.name(key_profile) |
||||||
|
.send() |
||||||
|
.await |
||||||
|
.expect_or_log("Parameter request fail"); |
||||||
|
|
||||||
|
let parameter = parameter_output |
||||||
|
.parameter() |
||||||
|
.expect_or_log("Parameter not found."); |
||||||
|
|
||||||
|
let parameter_value = parameter |
||||||
|
.value() |
||||||
|
.expect_or_log("Parameter has no value."); |
||||||
|
|
||||||
|
parameter_value.to_string() |
||||||
|
} |
||||||
Loading…
Reference in new issue