use http;
use http::Method;
use http::Method::*;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
pub struct Client {
url: String,
gssnegotiate: Option<bool>,
username: Option<String>,
}
impl Client {
pub fn new(url: &str, gssnegotiate: Option<bool>, username: Option<String>) -> Client {
Client {
url: http::remove_trailing_slash(url),
gssnegotiate,
username,
}
}
fn send<T: DeserializeOwned, U: Serialize>(&self, method: Method, path: &str, data: Option<U>) -> Result<T, String> {
http::send(method,
format!("{}{}", self.url, path).as_str(),
data,
self.gssnegotiate.as_ref(),
self.username.as_ref().map(String::as_ref))
}
fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, String> {
self.send(GET, path, None::<()>)
}
fn post<T: DeserializeOwned, U: Serialize>(&self, path: &str, data: Option<U>) -> Result<T, String> {
self.send(POST, path, data)
}
fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T, String> {
self.send(DELETE, path, None::<()>)
}
pub fn get_sessions(&self, from: Option<i64>, size: Option<i64>) -> Result<Sessions, String> {
let params = http::params(vec![
http::param("from", from),
http::param("size", size)
]);
self.get(format!("/sessions{}", params).as_str())
}
pub fn create_session(&self, new_session_request: NewSessionRequest) -> Result<Session, String> {
self.post("/sessions", Some(new_session_request))
}
pub fn get_session(&self, session_id: i64) -> Result<Session, String> {
self.get(format!("/sessions/{}", session_id).as_str())
}
pub fn get_session_state(&self, session_id: i64) -> Result<SessionStateOnly, String> {
self.get(format!("/sessions/{}/state", session_id).as_str())
}
pub fn kill_session(&self, session_id: i64) -> Result<SessionKillResult, String> {
self.delete(format!("/sessions/{}", session_id).as_str())
}
pub fn get_session_log(&self, session_id: i64, from: Option<i64>, size: Option<i64>)-> Result<SessionLog, String> {
let params = http::params(vec![
http::param("from", from),
http::param("size", size)
]);
self.get(format!("/sessions/{}/log{}", session_id, params).as_str())
}
pub fn get_statements(&self, session_id: i64) -> Result<Statements, String> {
self.get(format!("/sessions/{}/statements", session_id).as_str())
}
pub fn run_statement(&self, session_id: i64, run_statement_request: RunStatementRequest) -> Result<Statement, String> {
self.post(format!("/sessions/{}/statements", session_id).as_str(), Some(run_statement_request))
}
pub fn get_statement(&self, session_id: i64, statement_id: i64) -> Result<Statement, String> {
self.get(format!("/sessions/{}/statements/{}", session_id, statement_id).as_str())
}
pub fn cancel_statement(&self, session_id: i64, statement_id: i64) -> Result<StatementCancelResult, String> {
self.post(format!("/sessions/{}/statements/{}/cancel", session_id, statement_id).as_str(), None::<()>)
}
pub fn get_batches(&self, from: Option<i64>, size: Option<i64>) -> Result<Batches, String> {
let params = http::params(vec![
http::param("from", from),
http::param("size", size)
]);
self.get(format!("/batches{}", params).as_str())
}
pub fn create_batch(&self, new_batch_request: NewBatchRequest) -> Result<Batch, String> {
self.post("/batches", Some(new_batch_request))
}
pub fn get_batch(&self, batch_id: i64) -> Result<Batch, String> {
self.get(format!("/batches/{}", batch_id).as_str())
}
pub fn get_batch_state(&self, batch_id: i64) -> Result<BatchStateOnly, String> {
self.get(format!("/batches/{}/state", batch_id).as_str())
}
pub fn kill_batch(&self, batch_id: i64) -> Result<BatchKillResult, String> {
self.delete(format!("/batches/{}", batch_id).as_str())
}
pub fn get_batch_log(&self, batch_id: i64, from: Option<i64>, size: Option<i64>) -> Result<BatchLog, String> {
let params = http::params(vec![
http::param("from", from),
http::param("size", size)
]);
self.get(format!("/batches/{}/log{}", batch_id, params).as_str())
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Sessions {
pub from: Option<i64>,
pub total: Option<i64>,
pub sessions: Option<Vec<Session>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewSessionRequest {
pub kind: SessionKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy_user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jars: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub py_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver_memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver_cores: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_cores: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_executors: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub archives: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub queue: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conf: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub heartbeat_timeout_in_second: Option<i64>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Session {
pub id: Option<i64>,
pub app_id: Option<String>,
pub owner: Option<String>,
pub proxy_user: Option<String>,
pub kind: Option<SessionKind>,
pub log: Option<Vec<String>>,
pub state: Option<SessionState>,
pub app_info: Option<HashMap<String, Option<String>>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionStateOnly {
pub id: Option<i64>,
pub state: Option<SessionState>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct SessionKillResult {
pub msg: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionLog {
pub id: Option<i64>,
pub from: Option<i64>,
pub total: Option<i64>,
pub log: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Statements {
pub total_statements: Option<i64>,
pub statements: Option<Vec<Statement>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct RunStatementRequest {
pub code: String,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Statement {
pub id: Option<i64>,
pub state: Option<StatementState>,
pub output: Option<StatementOutput>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct StatementOutput {
pub status: Option<String>,
pub execution_count: Option<i64>,
pub data: Option<HashMap<String, Option<String>>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct StatementCancelResult {
pub msg: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Batches {
pub from: Option<i64>,
pub total: Option<i64>,
pub sessions: Option<Vec<Batch>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Batch {
pub id: Option<i64>,
pub app_id: Option<String>,
pub app_info: Option<HashMap<String, Option<String>>>,
pub log: Option<Vec<String>>,
pub state: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NewBatchRequest {
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy_user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub class_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jars: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub py_files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub files: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver_memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver_cores: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executor_cores: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_executors: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub archives: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub queue: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conf: Option<HashMap<String, String>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct BatchStateOnly {
pub id: Option<i64>,
pub state: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct BatchKillResult {
pub msg: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct BatchLog {
pub id: Option<i64>,
pub from: Option<i64>,
pub total: Option<i64>,
pub log: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionState {
NotStarted,
Starting,
Idle,
Busy,
ShuttingDown,
Error,
Dead,
Success,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum SessionKind {
Spark,
Pyspark,
Pyspark3,
Sparkr,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum StatementState {
Waiting,
Running,
Available,
Error,
Cancelling,
Cancelled,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_new() {
struct TestCase {
url: &'static str,
expected_url: String,
gssnegotiate: Option<bool>,
username: Option<String>,
}
let test_cases = vec![
TestCase {
url: "http://example.com:8998",
expected_url: "http://example.com:8998".to_string(),
gssnegotiate: None,
username: None,
},
TestCase {
url: "http://example.com:8998/",
expected_url: "http://example.com:8998".to_string(),
gssnegotiate: Some(false),
username: Some("".to_string()),
},
TestCase {
url: "http://example.com:8998",
expected_url: "http://example.com:8998".to_string(),
gssnegotiate: Some(true),
username: Some("user".to_string()),
},
];
for test_case in test_cases {
let client = Client::new(test_case.url, test_case.gssnegotiate.clone(), test_case.username.clone());
assert_eq!(test_case.expected_url, client.url);
assert_eq!(test_case.gssnegotiate, client.gssnegotiate);
assert_eq!(test_case.username, client.username);
}
}
}