Skip to content

Commit 0a43883

Browse files
authored
Refactor parts of Plane into a plane-common crate (DIS-2967) (#849)
The main `plane` crate contains a client, but depending on it in another project requires bringing in _all_ of Plane, including the openssl dependency. This breaks the client (and other pieces) out into a `plane-common` crate with the parts needed to "consume" Plane as a client, and a crate for Plane itself (in particular, without the database and ACME stuff, which bring in other dependencies that can be hard to wrangle). This looks like a huge PR but there is essentially no net new code here; files are just moved around. A few notes: - I got rid of `plane-dynamic`; it was a hack to make tests compile faster but it should be less necessary now that we have broken up Plane into several crates. - Roughly, the criteria for "what goes in common" is "the client and everything it depends on". `plane` depends on `plane-common`, but `plane-common` does NOT depend on `plane`, so things like `ExponentialBackoff` (used by the client to manage reconnects) go in `plane-common`. Todo: - [x] get `test_get_metrics` and `backend_lifecycle` tests to pass. - [x] rename to `plane-common`? - [x] p2 PR for refactor and ensure p2 integration tests pass
1 parent 377ae64 commit 0a43883

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+893
-586
lines changed

Cargo.lock

+323-60
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[workspace]
22
resolver = "2"
33
members = [
4+
"common",
45
"dynamic-proxy",
56
"plane/plane-tests",
6-
"plane/plane-dynamic",
77
"plane",
88
"plane/plane-tests/plane-test-macro",
99
]

common/Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "plane-common"
3+
version = "0.5.1"
4+
edition = "2021"
5+
6+
[dependencies]
7+
axum = { version = "0.7.7", features = ["ws"] }
8+
bollard = "0.17.0"
9+
chrono = { version = "0.4.31", features = ["serde"] }
10+
clap = "4.4.10"
11+
data-encoding = "2.4.0"
12+
futures-util = "0.3.29"
13+
rand = "0.8.5"
14+
reqwest = { version = "0.12.8", features = ["json"] }
15+
serde = "1.0.109"
16+
serde_json = "1.0.107"
17+
serde_with = "3.4.0"
18+
thiserror = "1.0.50"
19+
tokio = { version = "1.33.0", features = ["sync"] }
20+
tokio-tungstenite = "0.24.0"
21+
tracing = "0.1.40"
22+
tungstenite = "0.24.0"
23+
url = "2.4.1"
24+
valuable = { version = "0.1.0", features = ["derive"] }
25+
26+
[dev-dependencies]
27+
anyhow = "1.0.93"
28+
async-stream = "0.3.6"
29+
axum = "0.7.9"

plane/build.rs common/build.rs

File renamed without changes.
File renamed without changes.

common/src/exponential_backoff.rs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::time::{Duration, SystemTime};
2+
3+
pub struct ExponentialBackoff {
4+
initial_duration_millis: u128,
5+
max_duration: Duration,
6+
defer_duration: Duration,
7+
multiplier: f64,
8+
step: i32,
9+
deferred_reset: Option<SystemTime>,
10+
}
11+
12+
impl ExponentialBackoff {
13+
pub fn new(
14+
initial_duration: Duration,
15+
max_duration: Duration,
16+
multiplier: f64,
17+
defer_duration: Duration,
18+
) -> Self {
19+
let initial_duration_millis = initial_duration.as_millis();
20+
21+
Self {
22+
initial_duration_millis,
23+
max_duration,
24+
multiplier,
25+
step: 0,
26+
defer_duration,
27+
deferred_reset: None,
28+
}
29+
}
30+
31+
/// Reset the backoff, but only if `wait` is not called again for at least `defer_duration`.
32+
pub fn defer_reset(&mut self) {
33+
self.deferred_reset = Some(SystemTime::now() + self.defer_duration);
34+
}
35+
36+
pub async fn wait(&mut self) {
37+
if let Some(deferred_reset) = self.deferred_reset {
38+
self.deferred_reset = None;
39+
if SystemTime::now() > deferred_reset {
40+
self.reset();
41+
return;
42+
}
43+
}
44+
45+
let duration = self.initial_duration_millis as f64 * self.multiplier.powi(self.step);
46+
let duration = Duration::from_millis(duration as u64);
47+
let duration = duration.min(self.max_duration);
48+
tokio::time::sleep(duration).await;
49+
50+
self.step += 1;
51+
}
52+
53+
pub fn reset(&mut self) {
54+
self.deferred_reset = None;
55+
self.step = 0;
56+
}
57+
}
58+
59+
impl Default for ExponentialBackoff {
60+
fn default() -> Self {
61+
Self::new(
62+
Duration::from_secs(1),
63+
Duration::from_secs(60),
64+
1.1,
65+
Duration::from_secs(60),
66+
)
67+
}
68+
}

plane/src/client/mod.rs common/src/lib.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use self::controller_address::AuthorizedAddress;
22
use crate::{
3-
controller::{error::ApiError, StatusResponse},
43
names::{BackendName, DroneName},
54
protocol::{MessageFromDns, MessageFromDrone, MessageFromProxy},
65
typed_socket::client::TypedSocketConnector,
@@ -9,11 +8,22 @@ use crate::{
98
ConnectResponse, DrainResult, DronePoolName, RevokeRequest,
109
},
1110
};
11+
use protocol::{ApiError, StatusResponse};
1212
use reqwest::{Response, StatusCode};
1313
use serde::de::DeserializeOwned;
1414
use url::{form_urlencoded, Url};
15+
1516
pub mod controller_address;
17+
pub mod exponential_backoff;
18+
pub mod log_types;
19+
pub mod names;
20+
pub mod protocol;
21+
pub mod serialization;
1622
pub mod sse;
23+
pub mod typed_socket;
24+
pub mod types;
25+
pub mod util;
26+
pub mod version;
1727

1828
#[derive(thiserror::Error, Debug)]
1929
pub enum PlaneClientError {

plane/src/log_types.rs common/src/log_types.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use chrono::{DateTime, Utc};
22
use serde::{Deserialize, Serialize};
3-
use std::{net::SocketAddr, time::SystemTime};
4-
use time::OffsetDateTime;
3+
use std::net::SocketAddr;
54
use valuable::{Tuplable, TupleDef, Valuable, Value, Visit};
65

76
// See: https://github.com/tokio-rs/valuable/issues/86#issuecomment-1760446976
@@ -27,14 +26,6 @@ impl Tuplable for LoggableTime {
2726
}
2827
}
2928

30-
impl From<OffsetDateTime> for LoggableTime {
31-
fn from(offset: OffsetDateTime) -> Self {
32-
let t: SystemTime = offset.into();
33-
let dt: DateTime<Utc> = t.into();
34-
Self(dt)
35-
}
36-
}
37-
3829
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd)]
3930
pub struct BackendAddr(pub SocketAddr);
4031

plane/src/names.rs common/src/names.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{drone::runtime::docker::types::ContainerId, types::NodeKind};
1+
use crate::types::NodeKind;
22
use clap::error::ErrorKind;
33
use serde::{Deserialize, Serialize};
44
use std::fmt::{Debug, Display};
@@ -17,8 +17,9 @@ pub enum NameError {
1717
InvalidCharacter(char, usize),
1818

1919
#[error(
20-
"too long ({0} characters; max is {} including prefix)",
21-
MAX_NAME_LENGTH
20+
"too long ({length} characters; max is {max} including prefix)",
21+
length = "{0}",
22+
max = MAX_NAME_LENGTH
2223
)]
2324
TooLong(usize),
2425
}
@@ -163,17 +164,18 @@ entity_name!(DroneName, Some("dr"));
163164
entity_name!(AcmeDnsServerName, Some("ns"));
164165
entity_name!(BackendActionName, Some("ak"));
165166

166-
impl TryFrom<ContainerId> for BackendName {
167-
type Error = NameError;
168-
169-
fn try_from(value: ContainerId) -> Result<Self, Self::Error> {
170-
value
171-
.as_str()
167+
impl BackendName {
168+
pub fn from_container_id(container_id: String) -> Result<Self, NameError> {
169+
container_id
172170
.strip_prefix("plane-")
173-
.ok_or_else(|| NameError::InvalidPrefix(value.to_string(), "plane-".to_string()))?
171+
.ok_or_else(|| NameError::InvalidPrefix(container_id.clone(), "plane-".to_string()))?
174172
.to_string()
175173
.try_into()
176174
}
175+
176+
pub fn to_container_id(&self) -> String {
177+
format!("plane-{}", self)
178+
}
177179
}
178180

179181
pub trait NodeName: Name {
@@ -294,7 +296,7 @@ mod tests {
294296

295297
#[test]
296298
fn test_backend_name_from_invalid_container_id() {
297-
let container_id = ContainerId::from("invalid-123".to_string());
299+
let container_id = "invalid-123".to_string();
298300
assert_eq!(
299301
Err(NameError::InvalidPrefix(
300302
"invalid-123".to_string(),

plane/src/protocol.rs common/src/protocol.rs

+69-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,45 @@
1+
use std::fmt::Display;
2+
13
use crate::{
2-
database::backend::{BackendActionMessage, BackendMetricsMessage},
34
log_types::{BackendAddr, LoggableTime},
45
names::{BackendActionName, BackendName},
56
typed_socket::ChannelMessage,
67
types::{
78
backend_state::TerminationReason, BackendState, BearerToken, ClusterName, KeyConfig,
8-
SecretToken, Subdomain, TerminationKind,
9+
NodeId, SecretToken, Subdomain, TerminationKind,
910
},
1011
};
1112
use serde::{Deserialize, Serialize};
1213
use serde_json::Value;
1314

15+
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
16+
pub enum ApiErrorKind {
17+
FailedToAcquireKey,
18+
KeyUnheldNoSpawnConfig,
19+
KeyHeldUnhealthy,
20+
KeyHeld,
21+
NoDroneAvailable,
22+
FailedToRemoveKey,
23+
DatabaseError,
24+
NoClusterProvided,
25+
NotFound,
26+
InvalidClusterName,
27+
Other,
28+
}
29+
30+
#[derive(thiserror::Error, Debug, Serialize, Deserialize)]
31+
pub struct ApiError {
32+
pub id: String,
33+
pub kind: ApiErrorKind,
34+
pub message: String,
35+
}
36+
37+
impl Display for ApiError {
38+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39+
write!(f, "{:?}", self)
40+
}
41+
}
42+
1443
#[derive(Serialize, Deserialize, Debug, Clone, valuable::Valuable, PartialEq)]
1544
pub struct KeyDeadlines {
1645
/// When the key should be renewed.
@@ -132,6 +161,29 @@ pub enum MessageFromDrone {
132161
RenewKey(RenewKeyRequest),
133162
}
134163

164+
#[derive(Serialize, Deserialize, Debug, Clone)]
165+
pub struct BackendMetricsMessage {
166+
pub backend_id: BackendName,
167+
/// Memory used by backend excluding inactive file cache, same as use shown by docker stats
168+
/// ref: https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go#L227C45-L227C45
169+
pub mem_used: u64,
170+
/// Memory used by backend in bytes
171+
/// (calculated using kernel memory used by cgroup + page cache memory used by cgroup)
172+
pub mem_total: u64,
173+
/// Active memory (non reclaimable)
174+
pub mem_active: u64,
175+
/// Inactive memory (reclaimable)
176+
pub mem_inactive: u64,
177+
/// Unevictable memory (mlock etc)
178+
pub mem_unevictable: u64,
179+
/// The backend's memory limit
180+
pub mem_limit: u64,
181+
/// Nanoseconds of CPU used by backend since last message
182+
pub cpu_used: u64,
183+
/// Total CPU nanoseconds for system since last message
184+
pub sys_cpu: u64,
185+
}
186+
135187
impl ChannelMessage for MessageFromDrone {
136188
type Reply = MessageToDrone;
137189
}
@@ -146,6 +198,14 @@ pub struct RenewKeyResponse {
146198
pub deadlines: Option<KeyDeadlines>,
147199
}
148200

201+
#[derive(Debug, Clone, Serialize, Deserialize)]
202+
pub struct BackendActionMessage {
203+
pub action_id: BackendActionName,
204+
pub backend_id: BackendName,
205+
pub drone_id: NodeId,
206+
pub action: BackendAction,
207+
}
208+
149209
#[derive(Serialize, Deserialize, Debug, Clone)]
150210
pub enum MessageToDrone {
151211
Action(BackendActionMessage),
@@ -263,3 +323,10 @@ pub enum MessageToDns {
263323
impl ChannelMessage for MessageToDns {
264324
type Reply = MessageFromDns;
265325
}
326+
327+
#[derive(Serialize, Deserialize, Debug)]
328+
pub struct StatusResponse {
329+
pub status: String,
330+
pub version: String,
331+
pub hash: String,
332+
}
File renamed without changes.

plane/src/client/sse.rs common/src/sse.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::PlaneClientError;
2-
use crate::util::ExponentialBackoff;
2+
use crate::exponential_backoff::ExponentialBackoff;
33
use reqwest::{
44
header::{HeaderValue, ACCEPT, CONNECTION},
55
Client, Response,

plane/src/typed_socket/client.rs common/src/typed_socket/client.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use super::{ChannelMessage, Handshake, SocketAction, TypedSocket};
2-
use crate::client::controller_address::AuthorizedAddress;
3-
use crate::client::PlaneClientError;
2+
use crate::controller_address::AuthorizedAddress;
3+
use crate::exponential_backoff::ExponentialBackoff;
44
use crate::names::NodeName;
5-
use crate::{plane_version_info, util::ExponentialBackoff};
5+
use crate::version::plane_version_info;
6+
use crate::PlaneClientError;
67
use futures_util::{SinkExt, StreamExt};
78
use std::marker::PhantomData;
89
use tokio::net::TcpStream;
@@ -191,7 +192,7 @@ async fn new_client<T: ChannelMessage>(
191192

192193
#[cfg(test)]
193194
mod test {
194-
use crate::client::controller_address::AuthorizedAddress;
195+
use crate::controller_address::AuthorizedAddress;
195196

196197
#[test]
197198
fn test_url_no_token() {

plane/src/typed_socket/mod.rs common/src/typed_socket/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::client::PlaneClientError;
2-
use crate::PlaneVersionInfo;
1+
use crate::version::PlaneVersionInfo;
2+
use crate::PlaneClientError;
33
use serde::de::DeserializeOwned;
44
use serde::{Deserialize, Serialize};
55
use std::fmt::Debug;

0 commit comments

Comments
 (0)