Refactor admin (#52)

This commit is contained in:
Lev Kokotov
2022-03-01 08:47:19 -08:00
committed by GitHub
parent b21e0f4a7e
commit aaeef69d59
4 changed files with 176 additions and 281 deletions

View File

@@ -5,9 +5,8 @@ use tokio::net::tcp::OwnedWriteHalf;
use std::collections::HashMap; use std::collections::HashMap;
use crate::config::{get_config, parse, Role}; use crate::config::{get_config, parse, Role};
use crate::constants::{OID_INT4, OID_NUMERIC, OID_TEXT};
use crate::errors::Error; use crate::errors::Error;
use crate::messages::{custom_protocol_response_ok, error_response, write_all_half}; use crate::messages::*;
use crate::pool::ConnectionPool; use crate::pool::ConnectionPool;
use crate::stats::get_stats; use crate::stats::get_stats;
@@ -56,115 +55,66 @@ async fn show_databases(stream: &mut OwnedWriteHalf, pool: &ConnectionPool) -> R
let config = &*guard.clone(); let config = &*guard.clone();
drop(guard); drop(guard);
let columns = [ // Columns
"name", let columns = vec![
"host", ("name", DataType::Text),
"port", ("host", DataType::Text),
"database", ("port", DataType::Text),
"force_user", ("database", DataType::Text),
"pool_size", ("force_user", DataType::Text),
"min_pool_size", ("pool_size", DataType::Int4),
"reserve_pool", ("min_pool_size", DataType::Int4),
"pool_mode", ("reserve_pool", DataType::Int4),
"max_connections", ("pool_mode", DataType::Text),
"current_connections", ("max_connections", DataType::Int4),
"paused", ("current_connections", DataType::Int4),
"disabled", ("paused", DataType::Int4),
]; ("disabled", DataType::Int4),
let types = [
OID_TEXT, OID_TEXT, OID_TEXT, OID_TEXT, OID_TEXT, OID_INT4, OID_INT4, OID_INT4, OID_TEXT,
OID_INT4, OID_INT4, OID_INT4, OID_INT4,
]; ];
let mut res = BytesMut::new(); let mut res = BytesMut::new();
let mut row_desc = BytesMut::new();
row_desc.put_i16(columns.len() as i16);
for (i, column) in columns.iter().enumerate() { // RowDescription
row_desc.put_slice(&format!("{}\0", column).as_bytes()); res.put(row_description(&columns));
// Doesn't belong to any table
row_desc.put_i32(0);
// Doesn't belong to any table
row_desc.put_i16(0);
// Data type
row_desc.put_i32(types[i]);
// text size = variable (-1)
row_desc.put_i16(if types[i] == OID_TEXT { -1 } else { 4 });
// Type modifier: none that I know
row_desc.put_i32(-1);
// Format being used: text (0), binary (1)
row_desc.put_i16(0);
}
res.put_u8(b'T');
res.put_i32(row_desc.len() as i32 + 4);
res.put(row_desc);
for shard in 0..pool.shards() { for shard in 0..pool.shards() {
let database_name = &config.shards[&shard.to_string()].database; let database_name = &config.shards[&shard.to_string()].database;
let mut replica_count = 0; let mut replica_count = 0;
for server in 0..pool.servers(shard) { for server in 0..pool.servers(shard) {
// DataRow
let mut data_row = BytesMut::new();
data_row.put_i16(columns.len() as i16);
let address = pool.address(shard, server); let address = pool.address(shard, server);
let role = address.role.to_string(); let name = match address.role {
let name = match role.as_ref() { Role::Primary => format!("shard_{}_primary", shard),
"primary" => format!("shard_{}_primary", shard),
"replica" => format!("shard_{}_replica_{}", shard, replica_count), Role::Replica => {
_ => unreachable!(), let name = format!("shard_{}_replica_{}", shard, replica_count);
replica_count += 1;
name
}
}; };
let connections = pool.connections(shard, server); let pool_state = pool.pool_state(shard, server);
let data = HashMap::from([ res.put(data_row(&vec![
("host", address.host.to_string()), name, // name
("port", address.port.to_string()), address.host.to_string(), // host
("role", role), address.port.to_string(), // port
("name", name), database_name.to_string(), // database
("database", database_name.to_string()), config.user.name.to_string(), // force_user
("force_user", config.user.name.to_string()), config.general.pool_size.to_string(), // pool_size
("pool_size", config.general.pool_size.to_string()), "0".to_string(), // min_pool_size
("min_pool_size", "0".to_string()), "0".to_string(), // reserve_pool
("reserve_pool", "0".to_string()), config.general.pool_mode.to_string(), // pool_mode
("pool_mode", config.general.pool_mode.to_string()), config.general.pool_size.to_string(), // max_connections
// There is only one user support at the moment, pool_state.connections.to_string(), // current_connections
// so max_connections = num of users * pool_size = 1 * pool_size. "0".to_string(), // paused
("max_connections", config.general.pool_size.to_string()), "0".to_string(), // disabled
("current_connections", connections.connections.to_string()), ]));
("paused", "0".to_string()),
("disabled", "0".to_string()),
]);
for column in &columns {
let value = data[column].as_bytes();
data_row.put_i32(value.len() as i32);
data_row.put_slice(&value);
}
res.put_u8(b'D');
res.put_i32(data_row.len() as i32 + 4);
res.put(data_row);
if address.role == Role::Replica {
replica_count += 1;
}
} }
} }
let command_complete = BytesMut::from(&"SHOW\0"[..]); res.put(command_complete("SHOW"));
res.put_u8(b'C');
res.put_i32(command_complete.len() as i32 + 4);
res.put(command_complete);
// ReadyForQuery
res.put_u8(b'Z'); res.put_u8(b'Z');
res.put_i32(5); res.put_i32(5);
res.put_u8(b'I'); res.put_u8(b'I');
@@ -194,10 +144,7 @@ async fn reload(stream: &mut OwnedWriteHalf) -> Result<(), Error> {
let mut res = BytesMut::new(); let mut res = BytesMut::new();
// CommandComplete // CommandComplete
let command_complete = BytesMut::from(&"RELOAD\0"[..]); res.put(command_complete("RELOAD"));
res.put_u8(b'C');
res.put_i32(command_complete.len() as i32 + 4);
res.put(command_complete);
// ReadyForQuery // ReadyForQuery
res.put_u8(b'Z'); res.put_u8(b'Z');
@@ -217,74 +164,31 @@ async fn show_config(stream: &mut OwnedWriteHalf) -> Result<(), Error> {
let immutables = ["host", "port", "connect_timeout"]; let immutables = ["host", "port", "connect_timeout"];
// Columns // Columns
let columns = ["key", "value", "default", "changeable"]; let columns = vec![
("key", DataType::Text),
// RowDescription ("value", DataType::Text),
let mut row_desc = BytesMut::new(); ("default", DataType::Text),
row_desc.put_i16(4 as i16); // key, value, default, changeable ("changeable", DataType::Text),
];
for column in columns {
row_desc.put_slice(&format!("{}\0", column).as_bytes());
// Doesn't belong to any table
row_desc.put_i32(0);
// Doesn't belong to any table
row_desc.put_i16(0);
// Data type
row_desc.put_i32(OID_TEXT);
// text size = variable (-1)
row_desc.put_i16(-1);
// Type modifier: none that I know
row_desc.put_i32(-1);
// Format being used: text (0), binary (1)
row_desc.put_i16(0);
}
// Response data // Response data
let mut res = BytesMut::new(); let mut res = BytesMut::new();
res.put_u8(b'T'); res.put(row_description(&columns));
res.put_i32(row_desc.len() as i32 + 4);
res.put(row_desc);
// DataRow rows // DataRow rows
for (key, value) in config { for (key, value) in config {
let mut data_row = BytesMut::new();
data_row.put_i16(4 as i16); // key, value, default, changeable
let key_bytes = key.as_bytes();
let value = value.as_bytes();
data_row.put_i32(key_bytes.len() as i32);
data_row.put_slice(&key_bytes);
data_row.put_i32(value.len() as i32);
data_row.put_slice(&value);
data_row.put_i32(1 as i32);
data_row.put_slice(&"-".as_bytes());
let changeable = if immutables.iter().filter(|col| *col == &key).count() == 1 { let changeable = if immutables.iter().filter(|col| *col == &key).count() == 1 {
"no".as_bytes() "no".to_string()
} else { } else {
"yes".as_bytes() "yes".to_string()
}; };
data_row.put_i32(changeable.len() as i32);
data_row.put_slice(&changeable);
res.put_u8(b'D'); let row = vec![key, value, "-".to_string(), changeable];
res.put_i32(data_row.len() as i32 + 4);
res.put(data_row); res.put(data_row(&row));
} }
res.put_u8(b'C'); res.put(command_complete("SHOW"));
res.put_i32("SHOW CONFIG\0".as_bytes().len() as i32 + 4);
res.put_slice(&"SHOW CONFIG\0".as_bytes());
res.put_u8(b'Z'); res.put_u8(b'Z');
res.put_i32(5); res.put_i32(5);
@@ -295,81 +199,38 @@ async fn show_config(stream: &mut OwnedWriteHalf) -> Result<(), Error> {
/// SHOW STATS /// SHOW STATS
async fn show_stats(stream: &mut OwnedWriteHalf) -> Result<(), Error> { async fn show_stats(stream: &mut OwnedWriteHalf) -> Result<(), Error> {
let columns = [ let columns = vec![
"database", ("database", DataType::Text),
"total_xact_count", ("total_xact_count", DataType::Numeric),
"total_query_count", ("total_query_count", DataType::Numeric),
"total_received", ("total_received", DataType::Numeric),
"total_sent", ("total_sent", DataType::Numeric),
"total_xact_time", ("total_xact_time", DataType::Numeric),
"total_query_time", ("total_query_time", DataType::Numeric),
"total_wait_time", ("total_wait_time", DataType::Numeric),
"avg_xact_count", ("avg_xact_count", DataType::Numeric),
"avg_query_count", ("avg_query_count", DataType::Numeric),
"avg_recv", ("avg_recv", DataType::Numeric),
"avg_sent", ("avg_sent", DataType::Numeric),
"avg_xact_time", ("avg_xact_time", DataType::Numeric),
"avg_query_time", ("avg_query_time", DataType::Numeric),
"avg_wait_time", ("avg_wait_time", DataType::Numeric),
]; ];
let stats = get_stats(); let stats = get_stats();
let mut res = BytesMut::new(); let mut res = BytesMut::new();
let mut row_desc = BytesMut::new(); res.put(row_description(&columns));
let mut data_row = BytesMut::new();
// Number of columns: 1 let mut row = vec![
row_desc.put_i16(columns.len() as i16); String::from("all shards"), // TODO: per-database stats,
data_row.put_i16(columns.len() as i16); ];
for (i, column) in columns.iter().enumerate() { for column in &columns[1..] {
// RowDescription row.push(stats.get(column.0).unwrap_or(&0).to_string());
// Column name
row_desc.put_slice(&format!("{}\0", column).as_bytes());
// Doesn't belong to any table
row_desc.put_i32(0);
// Doesn't belong to any table
row_desc.put_i16(0);
// Data type
row_desc.put_i32(if i == 0 { OID_TEXT } else { OID_NUMERIC });
// Numeric/text size = variable (-1)
row_desc.put_i16(-1);
// Type modifier: none that I know
row_desc.put_i32(-1);
// Format being used: text (0), binary (1)
row_desc.put_i16(0);
// DataRow
let value = if i == 0 {
String::from("all shards")
} else {
stats.get(&column.to_string()).unwrap_or(&0).to_string()
};
data_row.put_i32(value.len() as i32);
data_row.put_slice(value.as_bytes());
} }
let command_complete = BytesMut::from(&"SHOW\0"[..]); res.put(data_row(&row));
res.put(command_complete("SHOW"));
res.put_u8(b'T');
res.put_i32(row_desc.len() as i32 + 4);
res.put(row_desc);
res.put_u8(b'D');
res.put_i32(data_row.len() as i32 + 4);
res.put(data_row);
res.put_u8(b'C');
res.put_i32(command_complete.len() as i32 + 4);
res.put(command_complete);
res.put_u8(b'Z'); res.put_u8(b'Z');
res.put_i32(5); res.put_i32(5);

View File

@@ -24,7 +24,4 @@ pub const MESSAGE_TERMINATOR: u8 = 0;
// //
// Data types // Data types
// //
pub const OID_NUMERIC: i32 = 1700;
pub const OID_TEXT: i32 = 25;
pub const OID_INT4: i32 = 23; // int
pub const _OID_INT8: i32 = 20; // bigint pub const _OID_INT8: i32 = 20; // bigint

View File

@@ -8,9 +8,26 @@ use tokio::net::{
TcpStream, TcpStream,
}; };
use crate::errors::Error;
use std::collections::HashMap; use std::collections::HashMap;
use crate::errors::Error; /// Postgres data type mappings
/// used in RowDescription ('T') message.
pub enum DataType {
Text,
Int4,
Numeric,
}
impl From<&DataType> for i32 {
fn from(data_type: &DataType) -> i32 {
match data_type {
DataType::Text => 25,
DataType::Int4 => 23,
DataType::Numeric => 1700,
}
}
}
/// Tell the client that authentication handshake completed successfully. /// Tell the client that authentication handshake completed successfully.
pub async fn auth_ok(stream: &mut TcpStream) -> Result<(), Error> { pub async fn auth_ok(stream: &mut TcpStream) -> Result<(), Error> {
@@ -259,68 +276,17 @@ pub async fn show_response(
// 3. CommandComplete // 3. CommandComplete
// 4. ReadyForQuery // 4. ReadyForQuery
// RowDescription
let mut row_desc = BytesMut::new();
// Number of columns: 1
row_desc.put_i16(1);
// Column name
row_desc.put_slice(&format!("{}\0", name).as_bytes());
// Doesn't belong to any table
row_desc.put_i32(0);
// Doesn't belong to any table
row_desc.put_i16(0);
// Text
row_desc.put_i32(25);
// Text size = variable (-1)
row_desc.put_i16(-1);
// Type modifier: none that I know
row_desc.put_i32(-1);
// Format being used: text (0), binary (1)
row_desc.put_i16(0);
// DataRow
let mut data_row = BytesMut::new();
// Number of columns
data_row.put_i16(1);
// Size of the column content (length of the string really)
data_row.put_i32(value.len() as i32);
// The content
data_row.put_slice(value.as_bytes());
// CommandComplete
let mut command_complete = BytesMut::new();
// Number of rows returned (just one)
command_complete.put_slice(&b"SELECT 1\0"[..]);
// The final messages sent to the client // The final messages sent to the client
let mut res = BytesMut::new(); let mut res = BytesMut::new();
// RowDescription // RowDescription
res.put_u8(b'T'); res.put(row_description(&vec![(name, DataType::Text)]));
res.put_i32(row_desc.len() as i32 + 4);
res.put(row_desc);
// DataRow // DataRow
res.put_u8(b'D'); res.put(data_row(&vec![value.to_string()]));
res.put_i32(data_row.len() as i32 + 4);
res.put(data_row);
// CommandComplete // CommandComplete
res.put_u8(b'C'); res.put(command_complete("SELECT 1"));
res.put_i32(command_complete.len() as i32 + 4);
res.put(command_complete);
// ReadyForQuery // ReadyForQuery
res.put_u8(b'Z'); res.put_u8(b'Z');
@@ -330,6 +296,77 @@ pub async fn show_response(
write_all_half(stream, res).await write_all_half(stream, res).await
} }
pub fn row_description(columns: &Vec<(&str, DataType)>) -> BytesMut {
let mut res = BytesMut::new();
let mut row_desc = BytesMut::new();
// how many colums we are storing
row_desc.put_i16(columns.len() as i16);
for (name, data_type) in columns {
// Column name
row_desc.put_slice(&format!("{}\0", name).as_bytes());
// Doesn't belong to any table
row_desc.put_i32(0);
// Doesn't belong to any table
row_desc.put_i16(0);
// Text
row_desc.put_i32(data_type.into());
// Text size = variable (-1)
let type_size = match data_type {
DataType::Text => -1,
DataType::Int4 => 4,
DataType::Numeric => -1,
};
row_desc.put_i16(type_size);
// Type modifier: none that I know
row_desc.put_i32(-1);
// Format being used: text (0), binary (1)
row_desc.put_i16(0);
}
res.put_u8(b'T');
res.put_i32(row_desc.len() as i32 + 4);
res.put(row_desc);
res
}
pub fn data_row(row: &Vec<String>) -> BytesMut {
let mut res = BytesMut::new();
let mut data_row = BytesMut::new();
data_row.put_i16(row.len() as i16);
for column in row {
let column = column.as_bytes();
data_row.put_i32(column.len() as i32);
data_row.put_slice(&column);
}
res.put_u8(b'D');
res.put_i32(data_row.len() as i32 + 4);
res.put(data_row);
res
}
pub fn command_complete(command: &str) -> BytesMut {
let cmd = BytesMut::from(format!("{}\0", command).as_bytes());
let mut res = BytesMut::new();
res.put_u8(b'C');
res.put_i32(cmd.len() as i32 + 4);
res.put(cmd);
res
}
/// Write all data in the buffer to the TcpStream. /// Write all data in the buffer to the TcpStream.
pub async fn write_all(stream: &mut TcpStream, buf: BytesMut) -> Result<(), Error> { pub async fn write_all(stream: &mut TcpStream, buf: BytesMut) -> Result<(), Error> {
match stream.write_all(&buf).await { match stream.write_all(&buf).await {

View File

@@ -337,7 +337,7 @@ impl ConnectionPool {
self.addresses[shard].len() self.addresses[shard].len()
} }
pub fn connections(&self, shard: usize, server: usize) -> bb8::State { pub fn pool_state(&self, shard: usize, server: usize) -> bb8::State {
self.databases[shard][server].state() self.databases[shard][server].state()
} }