Skip to content

Commit

Permalink
Add CronJob and StatefulSet targetting when copy_target is enabled. (m…
Browse files Browse the repository at this point in the history
…etalbear-co#2493)

* Add CronJob targetting when copy_target is enabled.

* add cronjob parsing from string like cronjob/foo

* docs and schema

* remove display

* Introduce StatefulSet targeting.

* CR - fix docs and error message

* changelog

* schema
  • Loading branch information
meowjesty authored Jun 14, 2024
1 parent 7c6d39b commit 4fdca3f
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allows a CronJob and StatefulSet to be used as a target when copy_target is enabled.
54 changes: 53 additions & 1 deletion mirrord-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,24 @@
}
]
},
"CronJobTarget": {
"type": "object",
"required": [
"cron_job"
],
"properties": {
"container": {
"type": [
"string",
"null"
]
},
"cron_job": {
"type": "string"
}
},
"additionalProperties": false
},
"DeploymentTarget": {
"description": "<!--${internal}--> Mirror the deployment specified by [`DeploymentTarget::deployment`].",
"type": "object",
Expand Down Expand Up @@ -1211,8 +1229,26 @@
},
"additionalProperties": false
},
"StatefulSetTarget": {
"type": "object",
"required": [
"stateful_set"
],
"properties": {
"container": {
"type": [
"string",
"null"
]
},
"stateful_set": {
"type": "string"
}
},
"additionalProperties": false
},
"Target": {
"description": "<!--${internal}--> ## path\n\nSpecifies the running pod (or deployment) to mirror.\n\nSupports: - `pod/{sample-pod}`; - `podname/{sample-pod}`; - `deployment/{sample-deployment}`; - `container/{sample-container}`; - `containername/{sample-container}`. - `job/{sample-job}`;",
"description": "<!--${internal}--> ## path\n\nSpecifies the running pod (or deployment) to mirror.\n\nSupports: - `pod/{sample-pod}`; - `podname/{sample-pod}`; - `deployment/{sample-deployment}`; - `container/{sample-container}`; - `containername/{sample-container}`. - `job/{sample-job}`; - `cronjob/{sample-cronjob}`; - `statefulset/{sample-statefulset}`;",
"anyOf": [
{
"description": "<!--${internal}--> Mirror a deployment.",
Expand Down Expand Up @@ -1246,6 +1282,22 @@
}
]
},
{
"description": "<!--${internal}--> Targets a [CronJob](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/).\n\nOnly supported when `copy_target` is enabled.",
"allOf": [
{
"$ref": "#/definitions/CronJobTarget"
}
]
},
{
"description": "<!--${internal}--> Targets a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/).\n\nOnly supported when `copy_target` is enabled.",
"allOf": [
{
"$ref": "#/definitions/StatefulSetTarget"
}
]
},
{
"description": "<!--${internal}--> Spawn a new pod.",
"type": "null"
Expand Down
30 changes: 21 additions & 9 deletions mirrord/cli/src/verify_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use mirrord_config::{
config::{ConfigContext, MirrordConfig},
feature::FeatureConfig,
target::{
deployment::DeploymentTarget, job::JobTarget, pod::PodTarget, rollout::RolloutTarget,
Target, TargetConfig,
cron_job::CronJobTarget, deployment::DeploymentTarget, job::JobTarget, pod::PodTarget,
rollout::RolloutTarget, stateful_set::StatefulSetTarget, Target, TargetConfig,
},
};
use serde::Serialize;
Expand Down Expand Up @@ -36,15 +36,23 @@ enum VerifiedTarget {

#[serde(untagged)]
Job(JobTarget),

#[serde(untagged)]
CronJob(CronJobTarget),

#[serde(untagged)]
StatefulSet(StatefulSetTarget),
}

impl From<Target> for VerifiedTarget {
fn from(value: Target) -> Self {
match value {
Target::Deployment(d) => Self::Deployment(d),
Target::Pod(p) => Self::Pod(p),
Target::Rollout(r) => Self::Rollout(r),
Target::Job(j) => Self::Job(j),
Target::Deployment(target) => Self::Deployment(target),
Target::Pod(target) => Self::Pod(target),
Target::Rollout(target) => Self::Rollout(target),
Target::Job(target) => Self::Job(target),
Target::CronJob(target) => Self::CronJob(target),
Target::StatefulSet(target) => Self::StatefulSet(target),
Target::Targetless => Self::Targetless,
}
}
Expand Down Expand Up @@ -72,8 +80,10 @@ enum TargetType {
Targetless,
Pod,
Deployment,
Job,
Rollout,
Job,
CronJob,
StatefulSet,
}

impl TargetType {
Expand All @@ -82,8 +92,10 @@ impl TargetType {
Self::Targetless,
Self::Pod,
Self::Deployment,
Self::Job,
Self::Rollout,
Self::Job,
Self::CronJob,
Self::StatefulSet,
]
.into_iter()
}
Expand All @@ -92,7 +104,7 @@ impl TargetType {
match self {
Self::Targetless | Self::Rollout => !config.copy_target.enabled,
Self::Pod => !(config.copy_target.enabled && config.copy_target.scale_down),
Self::Job => config.copy_target.enabled,
Self::Job | Self::CronJob | Self::StatefulSet => config.copy_target.enabled,
Self::Deployment => true,
}
}
Expand Down
4 changes: 2 additions & 2 deletions mirrord/config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ pub enum ConfigError {
TargetNamespaceWithoutTarget,

#[error(
"A Job target has been specified, but the feature `copy_target` has not been enabled!
"A Job or CronJob target has been specified, but the feature `copy_target` has not been enabled!
If you want to target a job, please enable `copy_target` feature in the `feature` section.
If you want to target a job or cronjob, please enable `copy_target` feature in the `feature` section.
"
)]
TargetJobWithoutCopyTarget,
Expand Down
9 changes: 8 additions & 1 deletion mirrord/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,14 @@ impl LayerConfig {
.target
.path
.as_ref()
.map(|target| matches!(target, target::Target::Job(_)))
.map(|target| {
matches!(
target,
target::Target::Job(_)
| target::Target::CronJob(_)
| target::Target::StatefulSet(_)
)
})
.unwrap_or_default()
{
Err(ConfigError::TargetJobWithoutCopyTarget)?
Expand Down
106 changes: 78 additions & 28 deletions mirrord/config/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::{
str::FromStr,
};

use cron_job::CronJobTarget;
use mirrord_analytics::CollectAnalytics;
use schemars::{gen::SchemaGenerator, schema::SchemaObject, JsonSchema};
use serde::{Deserialize, Serialize};
use stateful_set::StatefulSetTarget;

use self::{deployment::DeploymentTarget, job::JobTarget, pod::PodTarget, rollout::RolloutTarget};
use crate::{
Expand All @@ -17,10 +19,12 @@ use crate::{
util::string_or_struct_option,
};

pub mod cron_job;
pub mod deployment;
pub mod job;
pub mod pod;
pub mod rollout;
pub mod stateful_set;

#[derive(Deserialize, PartialEq, Eq, Clone, Debug, JsonSchema)]
#[serde(untagged, rename_all = "lowercase", deny_unknown_fields)]
Expand Down Expand Up @@ -184,6 +188,8 @@ mirrord-layer failed to parse the provided target!
>> deploy/<deployment-name>[/container/container-name]
>> pod/<pod-name>[/container/container-name]
>> job/<job-name>[/container/container-name]
>> cronjob/<cronjob-name>[/container/container-name]
>> statefulset/<statefulset-name>[/container/container-name]
- Note:
>> specifying container name is optional, defaults to the first container in the provided pod/deployment target.
Expand All @@ -207,6 +213,8 @@ mirrord-layer failed to parse the provided target!
/// - `container/{sample-container}`;
/// - `containername/{sample-container}`.
/// - `job/{sample-job}`;
/// - `cronjob/{sample-cronjob}`;
/// - `statefulset/{sample-statefulset}`;
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Debug, JsonSchema)]
#[serde(untagged, deny_unknown_fields)]
pub enum Target {
Expand All @@ -228,6 +236,20 @@ pub enum Target {
/// Only supported when `copy_target` is enabled.
Job(job::JobTarget),

/// <!--${internal}-->
/// Targets a
/// [CronJob](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/).
///
/// Only supported when `copy_target` is enabled.
CronJob(cron_job::CronJobTarget),

/// <!--${internal}-->
/// Targets a
/// [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/).
///
/// Only supported when `copy_target` is enabled.
StatefulSet(stateful_set::StatefulSetTarget),

/// <!--${internal}-->
/// Spawn a new pod.
Targetless,
Expand All @@ -248,6 +270,8 @@ impl FromStr for Target {
Some("rollout") => rollout::RolloutTarget::from_split(&mut split).map(Target::Rollout),
Some("pod") => pod::PodTarget::from_split(&mut split).map(Target::Pod),
Some("job") => job::JobTarget::from_split(&mut split).map(Target::Job),
Some("cronjob") => cron_job::CronJobTarget::from_split(&mut split).map(Target::CronJob),
Some("statefulset") => stateful_set::StatefulSetTarget::from_split(&mut split).map(Target::StatefulSet),
_ => Err(ConfigError::InvalidTarget(format!(
"Provided target: {target} is unsupported. Did you remember to add a prefix, e.g. pod/{target}? \n{FAIL_PARSE_DEPLOYMENT_OR_POD}",
))),
Expand All @@ -259,10 +283,12 @@ impl Target {
/// Get the target name - pod name, deployment name, rollout name..
pub fn get_target_name(&self) -> String {
match self {
Target::Deployment(deployment) => deployment.deployment.clone(),
Target::Pod(pod) => pod.pod.clone(),
Target::Rollout(rollout) => rollout.rollout.clone(),
Target::Job(job) => job.job.clone(),
Target::Deployment(target) => target.deployment.clone(),
Target::Pod(target) => target.pod.clone(),
Target::Rollout(target) => target.rollout.clone(),
Target::Job(target) => target.job.clone(),
Target::CronJob(target) => target.cron_job.clone(),
Target::StatefulSet(target) => target.stateful_set.clone(),
Target::Targetless => {
unreachable!("this shouldn't happen - called from operator on a flow where it's not targetless.")
}
Expand Down Expand Up @@ -312,15 +338,19 @@ impl_target_display!(PodTarget, pod);
impl_target_display!(DeploymentTarget, deployment);
impl_target_display!(RolloutTarget, rollout);
impl_target_display!(JobTarget, job);
impl_target_display!(CronJobTarget, cron_job);
impl_target_display!(StatefulSetTarget, stateful_set);

impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Target::Targetless => write!(f, "targetless"),
Target::Pod(pod) => pod.fmt_display(f),
Target::Deployment(dep) => dep.fmt_display(f),
Target::Rollout(roll) => roll.fmt_display(f),
Target::Job(job) => job.fmt_display(f),
Target::Pod(target) => target.fmt_display(f),
Target::Deployment(target) => target.fmt_display(f),
Target::Rollout(target) => target.fmt_display(f),
Target::Job(target) => target.fmt_display(f),
Target::CronJob(target) => target.fmt_display(f),
Target::StatefulSet(target) => target.fmt_display(f),
}
}
}
Expand All @@ -329,30 +359,36 @@ impl TargetDisplay for Target {
fn target_type(&self) -> &str {
match self {
Target::Targetless => "targetless",
Target::Deployment(x) => x.target_type(),
Target::Pod(x) => x.target_type(),
Target::Rollout(x) => x.target_type(),
Target::Job(x) => x.target_type(),
Target::Deployment(target) => target.target_type(),
Target::Pod(target) => target.target_type(),
Target::Rollout(target) => target.target_type(),
Target::Job(target) => target.target_type(),
Target::CronJob(target) => target.target_type(),
Target::StatefulSet(target) => target.target_type(),
}
}

fn target_name(&self) -> &str {
match self {
Target::Targetless => "targetless",
Target::Deployment(x) => x.target_name(),
Target::Pod(x) => x.target_name(),
Target::Rollout(x) => x.target_name(),
Target::Job(x) => x.target_name(),
Target::Deployment(target) => target.target_name(),
Target::Pod(target) => target.target_name(),
Target::Rollout(target) => target.target_name(),
Target::Job(target) => target.target_name(),
Target::CronJob(target) => target.target_name(),
Target::StatefulSet(target) => target.target_name(),
}
}

fn container_name(&self) -> Option<&String> {
match self {
Target::Targetless => None,
Target::Deployment(x) => x.container_name(),
Target::Pod(x) => x.container_name(),
Target::Rollout(x) => x.container_name(),
Target::Job(x) => x.container_name(),
Target::Deployment(target) => target.container_name(),
Target::Pod(target) => target.container_name(),
Target::Rollout(target) => target.container_name(),
Target::Job(target) => target.container_name(),
Target::CronJob(target) => target.container_name(),
Target::StatefulSet(target) => target.container_name(),
}
}
}
Expand All @@ -367,6 +403,8 @@ bitflags::bitflags! {
const CONTAINER = 8;
const ROLLOUT = 16;
const JOB = 32;
const CRON_JOB = 64;
const STATEFUL_SET = 128;
}
}

Expand All @@ -378,27 +416,39 @@ impl CollectAnalytics for &TargetConfig {
}
if let Some(path) = &self.path {
match path {
Target::Pod(pod) => {
Target::Pod(target) => {
flags |= TargetAnalyticFlags::POD;
if pod.container.is_some() {
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Target::Deployment(deployment) => {
Target::Deployment(target) => {
flags |= TargetAnalyticFlags::DEPLOYMENT;
if deployment.container.is_some() {
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Target::Rollout(rollout) => {
Target::Rollout(target) => {
flags |= TargetAnalyticFlags::ROLLOUT;
if rollout.container.is_some() {
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Target::Job(job) => {
Target::Job(target) => {
flags |= TargetAnalyticFlags::JOB;
if job.container.is_some() {
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Target::CronJob(target) => {
flags |= TargetAnalyticFlags::CRON_JOB;
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Target::StatefulSet(target) => {
flags |= TargetAnalyticFlags::STATEFUL_SET;
if target.container.is_some() {
flags |= TargetAnalyticFlags::CONTAINER;
}
}
Expand Down
Loading

0 comments on commit 4fdca3f

Please sign in to comment.