LCOV - code coverage report
Current view: top level - libs/pageserver_api/src - controller_api.rs (source / functions) Coverage Total Hit
Test: 12c2fc96834f59604b8ade5b9add28f1dce41ec6.info Lines: 36.7 % 79 29
Test Date: 2024-07-03 15:33:13 Functions: 4.3 % 254 11

            Line data    Source code
       1              : use std::str::FromStr;
       2              : 
       3              : /// Request/response types for the storage controller
       4              : /// API (`/control/v1` prefix).  Implemented by the server
       5              : /// in [`storage_controller::http`]
       6              : use serde::{Deserialize, Serialize};
       7              : use utils::id::{NodeId, TenantId};
       8              : 
       9              : use crate::{
      10              :     models::{ShardParameters, TenantConfig},
      11              :     shard::{ShardStripeSize, TenantShardId},
      12              : };
      13              : 
      14            6 : #[derive(Serialize, Deserialize, Debug)]
      15              : #[serde(deny_unknown_fields)]
      16              : pub struct TenantCreateRequest {
      17              :     pub new_tenant_id: TenantShardId,
      18              :     #[serde(default)]
      19              :     #[serde(skip_serializing_if = "Option::is_none")]
      20              :     pub generation: Option<u32>,
      21              : 
      22              :     // If omitted, create a single shard with TenantShardId::unsharded()
      23              :     #[serde(default)]
      24              :     #[serde(skip_serializing_if = "ShardParameters::is_unsharded")]
      25              :     pub shard_parameters: ShardParameters,
      26              : 
      27              :     #[serde(default)]
      28              :     #[serde(skip_serializing_if = "Option::is_none")]
      29              :     pub placement_policy: Option<PlacementPolicy>,
      30              : 
      31              :     #[serde(flatten)]
      32              :     pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it
      33              : }
      34              : 
      35            0 : #[derive(Serialize, Deserialize)]
      36              : pub struct TenantCreateResponseShard {
      37              :     pub shard_id: TenantShardId,
      38              :     pub node_id: NodeId,
      39              :     pub generation: u32,
      40              : }
      41              : 
      42            0 : #[derive(Serialize, Deserialize)]
      43              : pub struct TenantCreateResponse {
      44              :     pub shards: Vec<TenantCreateResponseShard>,
      45              : }
      46              : 
      47            0 : #[derive(Serialize, Deserialize)]
      48              : pub struct NodeRegisterRequest {
      49              :     pub node_id: NodeId,
      50              : 
      51              :     pub listen_pg_addr: String,
      52              :     pub listen_pg_port: u16,
      53              : 
      54              :     pub listen_http_addr: String,
      55              :     pub listen_http_port: u16,
      56              : }
      57              : 
      58            0 : #[derive(Serialize, Deserialize)]
      59              : pub struct NodeConfigureRequest {
      60              :     pub node_id: NodeId,
      61              : 
      62              :     pub availability: Option<NodeAvailabilityWrapper>,
      63              :     pub scheduling: Option<NodeSchedulingPolicy>,
      64              : }
      65              : 
      66            0 : #[derive(Serialize, Deserialize)]
      67              : pub struct TenantPolicyRequest {
      68              :     pub placement: Option<PlacementPolicy>,
      69              :     pub scheduling: Option<ShardSchedulingPolicy>,
      70              : }
      71              : 
      72            0 : #[derive(Serialize, Deserialize, Debug)]
      73              : pub struct TenantLocateResponseShard {
      74              :     pub shard_id: TenantShardId,
      75              :     pub node_id: NodeId,
      76              : 
      77              :     pub listen_pg_addr: String,
      78              :     pub listen_pg_port: u16,
      79              : 
      80              :     pub listen_http_addr: String,
      81              :     pub listen_http_port: u16,
      82              : }
      83              : 
      84            0 : #[derive(Serialize, Deserialize)]
      85              : pub struct TenantLocateResponse {
      86              :     pub shards: Vec<TenantLocateResponseShard>,
      87              :     pub shard_params: ShardParameters,
      88              : }
      89              : 
      90            0 : #[derive(Serialize, Deserialize)]
      91              : pub struct TenantDescribeResponse {
      92              :     pub tenant_id: TenantId,
      93              :     pub shards: Vec<TenantDescribeResponseShard>,
      94              :     pub stripe_size: ShardStripeSize,
      95              :     pub policy: PlacementPolicy,
      96              :     pub config: TenantConfig,
      97              : }
      98              : 
      99            0 : #[derive(Serialize, Deserialize)]
     100              : pub struct NodeDescribeResponse {
     101              :     pub id: NodeId,
     102              : 
     103              :     pub availability: NodeAvailabilityWrapper,
     104              :     pub scheduling: NodeSchedulingPolicy,
     105              : 
     106              :     pub listen_http_addr: String,
     107              :     pub listen_http_port: u16,
     108              : 
     109              :     pub listen_pg_addr: String,
     110              :     pub listen_pg_port: u16,
     111              : }
     112              : 
     113            0 : #[derive(Serialize, Deserialize)]
     114              : pub struct TenantDescribeResponseShard {
     115              :     pub tenant_shard_id: TenantShardId,
     116              : 
     117              :     pub node_attached: Option<NodeId>,
     118              :     pub node_secondary: Vec<NodeId>,
     119              : 
     120              :     pub last_error: String,
     121              : 
     122              :     /// A task is currently running to reconcile this tenant's intent state with the state on pageservers
     123              :     pub is_reconciling: bool,
     124              :     /// This shard failed in sending a compute notification to the cloud control plane, and a retry is pending.
     125              :     pub is_pending_compute_notification: bool,
     126              :     /// A shard split is currently underway
     127              :     pub is_splitting: bool,
     128              : 
     129              :     pub scheduling_policy: ShardSchedulingPolicy,
     130              : }
     131              : 
     132              : /// Explicitly migrating a particular shard is a low level operation
     133              : /// TODO: higher level "Reschedule tenant" operation where the request
     134              : /// specifies some constraints, e.g. asking it to get off particular node(s)
     135            0 : #[derive(Serialize, Deserialize, Debug)]
     136              : pub struct TenantShardMigrateRequest {
     137              :     pub tenant_shard_id: TenantShardId,
     138              :     pub node_id: NodeId,
     139              : }
     140              : 
     141              : /// Utilisation score indicating how good a candidate a pageserver
     142              : /// is for scheduling the next tenant. See [`crate::models::PageserverUtilization`].
     143              : /// Lower values are better.
     144            0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Debug)]
     145              : pub struct UtilizationScore(pub u64);
     146              : 
     147              : impl UtilizationScore {
     148           44 :     pub fn worst() -> Self {
     149           44 :         UtilizationScore(u64::MAX)
     150           44 :     }
     151              : }
     152              : 
     153            0 : #[derive(Serialize, Deserialize, Clone, Copy, Debug)]
     154              : #[serde(into = "NodeAvailabilityWrapper")]
     155              : pub enum NodeAvailability {
     156              :     // Normal, happy state
     157              :     Active(UtilizationScore),
     158              :     // Offline: Tenants shouldn't try to attach here, but they may assume that their
     159              :     // secondary locations on this node still exist.  Newly added nodes are in this
     160              :     // state until we successfully contact them.
     161              :     Offline,
     162              : }
     163              : 
     164              : impl PartialEq for NodeAvailability {
     165            0 :     fn eq(&self, other: &Self) -> bool {
     166              :         use NodeAvailability::*;
     167            0 :         matches!((self, other), (Active(_), Active(_)) | (Offline, Offline))
     168            0 :     }
     169              : }
     170              : 
     171              : impl Eq for NodeAvailability {}
     172              : 
     173              : // This wrapper provides serde functionality and it should only be used to
     174              : // communicate with external callers which don't know or care about the
     175              : // utilisation score of the pageserver it is targeting.
     176            0 : #[derive(Serialize, Deserialize, Clone, Copy, Debug)]
     177              : pub enum NodeAvailabilityWrapper {
     178              :     Active,
     179              :     Offline,
     180              : }
     181              : 
     182              : impl From<NodeAvailabilityWrapper> for NodeAvailability {
     183            0 :     fn from(val: NodeAvailabilityWrapper) -> Self {
     184            0 :         match val {
     185              :             // Assume the worst utilisation score to begin with. It will later be updated by
     186              :             // the heartbeats.
     187            0 :             NodeAvailabilityWrapper::Active => NodeAvailability::Active(UtilizationScore::worst()),
     188            0 :             NodeAvailabilityWrapper::Offline => NodeAvailability::Offline,
     189              :         }
     190            0 :     }
     191              : }
     192              : 
     193              : impl From<NodeAvailability> for NodeAvailabilityWrapper {
     194            0 :     fn from(val: NodeAvailability) -> Self {
     195            0 :         match val {
     196            0 :             NodeAvailability::Active(_) => NodeAvailabilityWrapper::Active,
     197            0 :             NodeAvailability::Offline => NodeAvailabilityWrapper::Offline,
     198              :         }
     199            0 :     }
     200              : }
     201              : 
     202            0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
     203              : pub enum ShardSchedulingPolicy {
     204              :     // Normal mode: the tenant's scheduled locations may be updated at will, including
     205              :     // for non-essential optimization.
     206              :     Active,
     207              : 
     208              :     // Disable optimizations, but permit scheduling when necessary to fulfil the PlacementPolicy.
     209              :     // For example, this still permits a node's attachment location to change to a secondary in
     210              :     // response to a node failure, or to assign a new secondary if a node was removed.
     211              :     Essential,
     212              : 
     213              :     // No scheduling: leave the shard running wherever it currently is.  Even if the shard is
     214              :     // unavailable, it will not be rescheduled to another node.
     215              :     Pause,
     216              : 
     217              :     // No reconciling: we will make no location_conf API calls to pageservers at all.  If the
     218              :     // shard is unavailable, it stays that way.  If a node fails, this shard doesn't get failed over.
     219              :     Stop,
     220              : }
     221              : 
     222              : impl Default for ShardSchedulingPolicy {
     223           22 :     fn default() -> Self {
     224           22 :         Self::Active
     225           22 :     }
     226              : }
     227              : 
     228            0 : #[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
     229              : pub enum NodeSchedulingPolicy {
     230              :     Active,
     231              :     Filling,
     232              :     Pause,
     233              :     PauseForRestart,
     234              :     Draining,
     235              : }
     236              : 
     237              : impl FromStr for NodeSchedulingPolicy {
     238              :     type Err = anyhow::Error;
     239              : 
     240            0 :     fn from_str(s: &str) -> Result<Self, Self::Err> {
     241            0 :         match s {
     242            0 :             "active" => Ok(Self::Active),
     243            0 :             "filling" => Ok(Self::Filling),
     244            0 :             "pause" => Ok(Self::Pause),
     245            0 :             "pause_for_restart" => Ok(Self::PauseForRestart),
     246            0 :             "draining" => Ok(Self::Draining),
     247            0 :             _ => Err(anyhow::anyhow!("Unknown scheduling state '{s}'")),
     248              :         }
     249            0 :     }
     250              : }
     251              : 
     252              : impl From<NodeSchedulingPolicy> for String {
     253            0 :     fn from(value: NodeSchedulingPolicy) -> String {
     254            0 :         use NodeSchedulingPolicy::*;
     255            0 :         match value {
     256            0 :             Active => "active",
     257            0 :             Filling => "filling",
     258            0 :             Pause => "pause",
     259            0 :             PauseForRestart => "pause_for_restart",
     260            0 :             Draining => "draining",
     261              :         }
     262            0 :         .to_string()
     263            0 :     }
     264              : }
     265              : 
     266              : /// Controls how tenant shards are mapped to locations on pageservers, e.g. whether
     267              : /// to create secondary locations.
     268            8 : #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
     269              : pub enum PlacementPolicy {
     270              :     /// Normal live state: one attached pageserver and zero or more secondaries.
     271              :     Attached(usize),
     272              :     /// Create one secondary mode locations. This is useful when onboarding
     273              :     /// a tenant, or for an idle tenant that we might want to bring online quickly.
     274              :     Secondary,
     275              : 
     276              :     /// Do not attach to any pageservers.  This is appropriate for tenants that
     277              :     /// have been idle for a long time, where we do not mind some delay in making
     278              :     /// them available in future.
     279              :     Detached,
     280              : }
     281              : 
     282            0 : #[derive(Serialize, Deserialize, Debug)]
     283              : pub struct TenantShardMigrateResponse {}
     284              : 
     285              : #[cfg(test)]
     286              : mod test {
     287              :     use super::*;
     288              :     use serde_json;
     289              : 
     290              :     /// Check stability of PlacementPolicy's serialization
     291              :     #[test]
     292            2 :     fn placement_policy_encoding() -> anyhow::Result<()> {
     293            2 :         let v = PlacementPolicy::Attached(1);
     294            2 :         let encoded = serde_json::to_string(&v)?;
     295            2 :         assert_eq!(encoded, "{\"Attached\":1}");
     296            2 :         assert_eq!(serde_json::from_str::<PlacementPolicy>(&encoded)?, v);
     297              : 
     298            2 :         let v = PlacementPolicy::Detached;
     299            2 :         let encoded = serde_json::to_string(&v)?;
     300            2 :         assert_eq!(encoded, "\"Detached\"");
     301            2 :         assert_eq!(serde_json::from_str::<PlacementPolicy>(&encoded)?, v);
     302            2 :         Ok(())
     303            2 :     }
     304              : 
     305              :     #[test]
     306            2 :     fn test_reject_unknown_field() {
     307            2 :         let id = TenantId::generate();
     308            2 :         let create_request = serde_json::json!({
     309            2 :             "new_tenant_id": id.to_string(),
     310            2 :             "unknown_field": "unknown_value".to_string(),
     311            2 :         });
     312            2 :         let err = serde_json::from_value::<TenantCreateRequest>(create_request).unwrap_err();
     313            2 :         assert!(
     314            2 :             err.to_string().contains("unknown field `unknown_field`"),
     315            0 :             "expect unknown field `unknown_field` error, got: {}",
     316              :             err
     317              :         );
     318            2 :     }
     319              : }
        

Generated by: LCOV version 2.1-beta