4949import org .elasticsearch .cluster .service .MasterServiceTaskQueue ;
5050import org .elasticsearch .common .Priority ;
5151import org .elasticsearch .common .TriConsumer ;
52- import org .elasticsearch .common .bytes .BytesReference ;
5352import org .elasticsearch .common .collect .ImmutableOpenMap ;
5453import org .elasticsearch .common .logging .DeprecationCategory ;
5554import org .elasticsearch .common .logging .DeprecationLogger ;
7877import org .elasticsearch .script .ScriptService ;
7978import org .elasticsearch .threadpool .Scheduler ;
8079import org .elasticsearch .threadpool .ThreadPool ;
81- import org .elasticsearch .xcontent .XContentBuilder ;
8280
83- import java .io .IOException ;
81+ import java .time .Instant ;
82+ import java .time .InstantSource ;
8483import java .util .ArrayList ;
8584import java .util .Collection ;
8685import java .util .Collections ;
@@ -569,16 +568,36 @@ public void validatePipelineRequest(ProjectId projectId, PutPipelineRequest requ
569568 validatePipeline (ingestInfos , projectId , request .getId (), config );
570569 }
571570
571+ public static void validateNoSystemPropertiesInPipelineConfig (final Map <String , Object > pipelineConfig ) {
572+ if (pipelineConfig .containsKey (Pipeline .CREATED_DATE_MILLIS )) {
573+ throw new ElasticsearchParseException ("Provided a pipeline property which is managed by the system: created_date_millis." );
574+ } else if (pipelineConfig .containsKey (Pipeline .CREATED_DATE )) {
575+ throw new ElasticsearchParseException ("Provided a pipeline property which is managed by the system: created_date." );
576+ } else if (pipelineConfig .containsKey (Pipeline .MODIFIED_DATE_MILLIS )) {
577+ throw new ElasticsearchParseException ("Provided a pipeline property which is managed by the system: modified_date_millis." );
578+ } else if (pipelineConfig .containsKey (Pipeline .MODIFIED_DATE )) {
579+ throw new ElasticsearchParseException ("Provided a pipeline property which is managed by the system: modified_date." );
580+ }
581+ }
582+
583+ /** Check whether updating a potentially existing pipeline will be a NOP.
584+ * Will return <code>false</code> if request contains system-properties like created or modified_date,
585+ * these should be rejected later.*/
572586 public static boolean isNoOpPipelineUpdate (ProjectMetadata metadata , PutPipelineRequest request ) {
573587 IngestMetadata currentIngestMetadata = metadata .custom (IngestMetadata .TYPE );
574588 if (request .getVersion () == null
575589 && currentIngestMetadata != null
576590 && currentIngestMetadata .getPipelines ().containsKey (request .getId ())) {
577- var pipelineConfig = XContentHelper .convertToMap (request .getSource (), false , request .getXContentType ()).v2 ();
578- var currentPipeline = currentIngestMetadata .getPipelines ().get (request .getId ());
579- if (currentPipeline .getConfig ().equals (pipelineConfig )) {
580- return true ;
581- }
591+
592+ var newPipelineConfig = XContentHelper .convertToMap (request .getSource (), false , request .getXContentType ()).v2 ();
593+
594+ Map <String , Object > currentConfigWithoutSystemProps = new HashMap <>(
595+ currentIngestMetadata .getPipelines ().get (request .getId ()).getConfig ()
596+ );
597+ currentConfigWithoutSystemProps .remove (Pipeline .CREATED_DATE_MILLIS );
598+ currentConfigWithoutSystemProps .remove (Pipeline .MODIFIED_DATE_MILLIS );
599+
600+ return newPipelineConfig .equals (currentConfigWithoutSystemProps );
582601 }
583602
584603 return false ;
@@ -676,10 +695,26 @@ private static void collectProcessorMetrics(
676695 */
677696 public static class PutPipelineClusterStateUpdateTask extends PipelineClusterStateUpdateTask {
678697 private final PutPipelineRequest request ;
679-
680- PutPipelineClusterStateUpdateTask (ProjectId projectId , ActionListener <AcknowledgedResponse > listener , PutPipelineRequest request ) {
698+ private final InstantSource instantSource ;
699+
700+ // constructor allowing for injection of InstantSource/time for testing
701+ PutPipelineClusterStateUpdateTask (
702+ final ProjectId projectId ,
703+ final ActionListener <AcknowledgedResponse > listener ,
704+ final PutPipelineRequest request ,
705+ final InstantSource instantSource
706+ ) {
681707 super (projectId , listener );
682708 this .request = request ;
709+ this .instantSource = instantSource ;
710+ }
711+
712+ PutPipelineClusterStateUpdateTask (
713+ final ProjectId projectId ,
714+ final ActionListener <AcknowledgedResponse > listener ,
715+ final PutPipelineRequest request
716+ ) {
717+ this (projectId , listener , request , Instant ::now );
683718 }
684719
685720 /**
@@ -691,10 +726,15 @@ public PutPipelineClusterStateUpdateTask(ProjectId projectId, PutPipelineRequest
691726
692727 @ Override
693728 public IngestMetadata execute (IngestMetadata currentIngestMetadata , Collection <IndexMetadata > allIndexMetadata ) {
694- BytesReference pipelineSource = request .getSource ();
729+ final Map <String , PipelineConfiguration > pipelines = currentIngestMetadata == null
730+ ? new HashMap <>(1 )
731+ : new HashMap <>(currentIngestMetadata .getPipelines ());
732+ final PipelineConfiguration existingPipeline = pipelines .get (request .getId ());
733+ final Map <String , Object > newPipelineConfig = XContentHelper .convertToMap (request .getSource (), true , request .getXContentType ())
734+ .v2 ();
735+
695736 if (request .getVersion () != null ) {
696- var currentPipeline = currentIngestMetadata != null ? currentIngestMetadata .getPipelines ().get (request .getId ()) : null ;
697- if (currentPipeline == null ) {
737+ if (existingPipeline == null ) {
698738 throw new IllegalArgumentException (
699739 String .format (
700740 Locale .ROOT ,
@@ -705,7 +745,7 @@ public IngestMetadata execute(IngestMetadata currentIngestMetadata, Collection<I
705745 );
706746 }
707747
708- final Integer currentVersion = currentPipeline .getVersion ();
748+ final Integer currentVersion = existingPipeline .getVersion ();
709749 if (Objects .equals (request .getVersion (), currentVersion ) == false ) {
710750 throw new IllegalArgumentException (
711751 String .format (
@@ -718,9 +758,8 @@ public IngestMetadata execute(IngestMetadata currentIngestMetadata, Collection<I
718758 );
719759 }
720760
721- var pipelineConfig = XContentHelper .convertToMap (request .getSource (), false , request .getXContentType ()).v2 ();
722- final Integer specifiedVersion = (Integer ) pipelineConfig .get ("version" );
723- if (pipelineConfig .containsKey ("version" ) && Objects .equals (specifiedVersion , currentVersion )) {
761+ final Integer specifiedVersion = (Integer ) newPipelineConfig .get ("version" );
762+ if (newPipelineConfig .containsKey ("version" ) && Objects .equals (specifiedVersion , currentVersion )) {
724763 throw new IllegalArgumentException (
725764 String .format (
726765 Locale .ROOT ,
@@ -733,24 +772,24 @@ public IngestMetadata execute(IngestMetadata currentIngestMetadata, Collection<I
733772
734773 // if no version specified in the pipeline definition, inject a version of [request.getVersion() + 1]
735774 if (specifiedVersion == null ) {
736- pipelineConfig .put ("version" , request .getVersion () == null ? 1 : request .getVersion () + 1 );
737- try {
738- var builder = XContentBuilder .builder (request .getXContentType ().xContent ()).map (pipelineConfig );
739- pipelineSource = BytesReference .bytes (builder );
740- } catch (IOException e ) {
741- throw new IllegalStateException (e );
742- }
775+ newPipelineConfig .put ("version" , request .getVersion () == null ? 1 : request .getVersion () + 1 );
743776 }
744777 }
745778
746- Map < String , PipelineConfiguration > pipelines ;
747- if (currentIngestMetadata ! = null ) {
748- pipelines = new HashMap <>( currentIngestMetadata . getPipelines () );
779+ final long nowMillis = instantSource . millis () ;
780+ if (existingPipeline = = null ) {
781+ newPipelineConfig . put ( Pipeline . CREATED_DATE_MILLIS , nowMillis );
749782 } else {
750- pipelines = new HashMap <>();
783+ Object existingCreatedAt = existingPipeline .getConfig ().get (Pipeline .CREATED_DATE_MILLIS );
784+ // only set/carry over `created_date` if existing pipeline already has it.
785+ // would be confusing if existing pipelines were all updated to have `created_date` set to now.
786+ if (existingCreatedAt != null ) {
787+ newPipelineConfig .put (Pipeline .CREATED_DATE_MILLIS , existingCreatedAt );
788+ }
751789 }
790+ newPipelineConfig .put (Pipeline .MODIFIED_DATE_MILLIS , nowMillis );
752791
753- pipelines .put (request .getId (), new PipelineConfiguration (request .getId (), pipelineSource , request . getXContentType () ));
792+ pipelines .put (request .getId (), new PipelineConfiguration (request .getId (), newPipelineConfig ));
754793 return new IngestMetadata (pipelines );
755794 }
756795 }
@@ -762,6 +801,7 @@ void validatePipeline(
762801 String pipelineId ,
763802 Map <String , Object > pipelineConfig
764803 ) throws Exception {
804+ validateNoSystemPropertiesInPipelineConfig (pipelineConfig );
765805 if (ingestInfos .isEmpty ()) {
766806 throw new IllegalStateException ("Ingest info is empty" );
767807 }
0 commit comments