Skip to content

Unify services and datasets, external and internal DNS are datasets too #3390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 29, 2023
Merged
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion common/src/sql/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ CREATE INDEX ON omicron.public.switch (
*/

CREATE TYPE omicron.public.service_kind AS ENUM (
'clickhouse',
'cockroach',
'crucible',
'crucible_pantry',
'dendrite',
'external_dns',
Expand Down Expand Up @@ -385,7 +388,9 @@ CREATE TABLE omicron.public.Zpool (
CREATE TYPE omicron.public.dataset_kind AS ENUM (
'crucible',
'cockroach',
'clickhouse'
'clickhouse',
'external_dns',
'internal_dns'
);

/*
Expand Down
2 changes: 2 additions & 0 deletions illumos-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ opte-ioctl.workspace = true

[dev-dependencies]
mockall.workspace = true
regress.workspace = true
serde_json.workspace = true
toml.workspace = true

[features]
Expand Down
99 changes: 98 additions & 1 deletion illumos-utils/src/zpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,45 @@ pub enum ZpoolKind {
///
/// This expects that the format will be: `ox{i,p}_<UUID>` - we parse the prefix
/// when reading the structure, and validate that the UUID can be utilized.
#[derive(Clone, Debug, Hash, PartialEq, Eq, JsonSchema)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ZpoolName {
id: Uuid,
kind: ZpoolKind,
}

const ZPOOL_NAME_REGEX: &str = r"^ox[ip]_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$";

/// Custom JsonSchema implementation to encode the constraints on Name.
impl JsonSchema for ZpoolName {
fn schema_name() -> String {
"ZpoolName".to_string()
}
fn json_schema(
_: &mut schemars::gen::SchemaGenerator,
) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(Box::new(schemars::schema::Metadata {
title: Some(
"The name of a Zpool".to_string(),
),
description: Some(
"Zpool names are of the format ox{i,p}_<UUID>. They are either \
Internal or External, and should be unique"
.to_string(),
),
..Default::default()
})),
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(ZPOOL_NAME_REGEX.to_owned()),
..Default::default()
})),
..Default::default()
}
.into()
}
}

impl ZpoolName {
pub fn new_internal(id: Uuid) -> Self {
Self { id, kind: ZpoolKind::Internal }
Expand Down Expand Up @@ -374,6 +407,70 @@ impl fmt::Display for ZpoolName {
mod test {
use super::*;

#[test]
fn test_zpool_name_regex() {
let valid = [
"oxi_d462a7f7-b628-40fe-80ff-4e4189e2d62b",
"oxp_d462a7f7-b628-40fe-80ff-4e4189e2d62b",
];

let invalid = [
"",
// Whitespace
" oxp_d462a7f7-b628-40fe-80ff-4e4189e2d62b",
"oxp_d462a7f7-b628-40fe-80ff-4e4189e2d62b ",
// Case sensitivity
"oxp_D462A7F7-b628-40fe-80ff-4e4189e2d62b",
// Bad prefix
"ox_d462a7f7-b628-40fe-80ff-4e4189e2d62b",
"oxa_d462a7f7-b628-40fe-80ff-4e4189e2d62b",
"oxi-d462a7f7-b628-40fe-80ff-4e4189e2d62b",
"oxp-d462a7f7-b628-40fe-80ff-4e4189e2d62b",
// Missing Prefix
"d462a7f7-b628-40fe-80ff-4e4189e2d62b",
// Bad UUIDs (Not following UUIDv4 format)
"oxi_d462a7f7-b628-30fe-80ff-4e4189e2d62b",
"oxi_d462a7f7-b628-40fe-c0ff-4e4189e2d62b",
];

let r = regress::Regex::new(ZPOOL_NAME_REGEX)
.expect("validation regex is valid");
for input in valid {
let m = r
.find(input)
.unwrap_or_else(|| panic!("input {input} did not match regex"));
assert_eq!(m.start(), 0, "input {input} did not match start");
assert_eq!(m.end(), input.len(), "input {input} did not match end");
}

for input in invalid {
assert!(
r.find(input).is_none(),
"invalid input {input} should not match validation regex"
);
}
}

#[test]
fn test_parse_zpool_name_json() {
#[derive(Serialize, Deserialize, JsonSchema)]
struct TestDataset {
pool_name: ZpoolName,
}

// Confirm that we can convert from a JSON string to a a ZpoolName
let json_string =
r#"{"pool_name":"oxi_d462a7f7-b628-40fe-80ff-4e4189e2d62b"}"#;
let dataset: TestDataset = serde_json::from_str(json_string)
.expect("Could not parse ZpoolName from Json Object");
assert!(matches!(dataset.pool_name.kind, ZpoolKind::Internal));

// Confirm we can go the other way (ZpoolName to JSON string) too.
let j = serde_json::to_string(&dataset)
.expect("Cannot convert back to JSON string");
assert_eq!(j, json_string);
}

fn toml_string(s: &str) -> String {
format!("zpool_name = \"{}\"", s)
}
Expand Down
8 changes: 8 additions & 0 deletions nexus/db-model/src/dataset_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ impl_enum_type!(
Crucible => b"crucible"
Cockroach => b"cockroach"
Clickhouse => b"clickhouse"
ExternalDns => b"external_dns"
InternalDns => b"internal_dns"
);

impl From<internal_api::params::DatasetKind> for DatasetKind {
Expand All @@ -33,6 +35,12 @@ impl From<internal_api::params::DatasetKind> for DatasetKind {
internal_api::params::DatasetKind::Clickhouse => {
DatasetKind::Clickhouse
}
internal_api::params::DatasetKind::ExternalDns => {
DatasetKind::ExternalDns
}
internal_api::params::DatasetKind::InternalDns => {
DatasetKind::InternalDns
}
}
}
}
12 changes: 12 additions & 0 deletions nexus/db-model/src/service_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ impl_enum_type!(
pub enum ServiceKind;

// Enum values
Clickhouse => b"clickhouse"
Cockroach => b"cockroach"
Crucible => b"crucible"
CruciblePantry => b"crucible_pantry"
Dendrite => b"dendrite"
ExternalDns => b"external_dns"
Expand Down Expand Up @@ -48,6 +51,15 @@ impl From<ServiceUsingCertificate> for ServiceKind {
impl From<internal_api::params::ServiceKind> for ServiceKind {
fn from(k: internal_api::params::ServiceKind) -> Self {
match k {
internal_api::params::ServiceKind::Clickhouse => {
ServiceKind::Clickhouse
}
internal_api::params::ServiceKind::Cockroach => {
ServiceKind::Cockroach
}
internal_api::params::ServiceKind::Crucible => {
ServiceKind::Crucible
}
internal_api::params::ServiceKind::ExternalDns { .. } => {
ServiceKind::ExternalDns
}
Expand Down
29 changes: 11 additions & 18 deletions nexus/types/src/internal_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use std::fmt;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::net::SocketAddrV6;
use std::str::FromStr;
use uuid::Uuid;

/// Describes the role of the sled within the rack.
Expand Down Expand Up @@ -134,6 +133,8 @@ pub enum DatasetKind {
Crucible,
Cockroach,
Clickhouse,
ExternalDns,
InternalDns,
}

impl fmt::Display for DatasetKind {
Expand All @@ -143,27 +144,13 @@ impl fmt::Display for DatasetKind {
Crucible => "crucible",
Cockroach => "cockroach",
Clickhouse => "clickhouse",
ExternalDns => "external_dns",
InternalDns => "internal_dns",
};
write!(f, "{}", s)
}
}

impl FromStr for DatasetKind {
type Err = omicron_common::api::external::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
use DatasetKind::*;
match s {
"crucible" => Ok(Crucible),
"cockroach" => Ok(Cockroach),
"clickhouse" => Ok(Clickhouse),
_ => Err(Self::Err::InternalError {
internal_message: format!("Unknown dataset kind: {}", s),
}),
}
}
}

/// Describes a dataset within a pool.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DatasetPutRequest {
Expand All @@ -188,13 +175,16 @@ pub struct ServiceNic {
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case", tag = "type", content = "content")]
pub enum ServiceKind {
Clickhouse,
Cockroach,
Crucible,
CruciblePantry,
ExternalDns { external_address: IpAddr, nic: ServiceNic },
InternalDns,
Nexus { external_address: IpAddr, nic: ServiceNic },
Oximeter,
Dendrite,
Tfport,
CruciblePantry,
BoundaryNtp { snat: SourceNatConfig, nic: ServiceNic },
InternalNtp,
}
Expand All @@ -203,6 +193,9 @@ impl fmt::Display for ServiceKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ServiceKind::*;
let s = match self {
Clickhouse => "clickhouse",
Cockroach => "cockroach",
Crucible => "crucible",
ExternalDns { .. } => "external_dns",
InternalDns => "internal_dns",
Nexus { .. } => "nexus",
Expand Down
74 changes: 59 additions & 15 deletions openapi/nexus-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,9 @@
"enum": [
"crucible",
"cockroach",
"clickhouse"
"clickhouse",
"external_dns",
"internal_dns"
]
},
"DatasetPutRequest": {
Expand Down Expand Up @@ -2775,6 +2777,62 @@
"ServiceKind": {
"description": "Describes the purpose of the service.",
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"clickhouse"
]
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"cockroach"
]
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"crucible"
]
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"crucible_pantry"
]
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
Expand Down Expand Up @@ -2893,20 +2951,6 @@
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"crucible_pantry"
]
}
},
"required": [
"type"
]
},
{
"type": "object",
"properties": {
Expand Down
Loading