Move watcher to use seq# and primary term for concurrency control (#37977)

* move watcher to seq# occ

* top level set

* fix parsing and missing setters

* share toXContent for PutResponse and rest end point

* fix redacted password

* fix username reference

* fix deactivate-watch.asciidoc have seq no references

* add seq# + term to activate-watch.asciidoc

* more doc fixes
This commit is contained in:
Boaz Leskes 2019-01-30 20:14:59 -05:00 committed by GitHub
parent dad41c2b7f
commit b11732104f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 624 additions and 127 deletions

View File

@ -76,6 +76,7 @@ import org.elasticsearch.index.reindex.AbstractBulkByScrollRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.ReindexRequest; import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.script.mustache.MultiSearchTemplateRequest; import org.elasticsearch.script.mustache.MultiSearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.elasticsearch.script.mustache.SearchTemplateRequest;
@ -885,6 +886,20 @@ final class RequestConverters {
return this; return this;
} }
Params withIfSeqNo(long ifSeqNo) {
if (ifSeqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
return putParam("if_seq_no", Long.toString(ifSeqNo));
}
return this;
}
Params withIfPrimaryTerm(long ifPrimaryTerm) {
if (ifPrimaryTerm != SequenceNumbers.UNASSIGNED_PRIMARY_TERM) {
return putParam("if_primary_term", Long.toString(ifPrimaryTerm));
}
return this;
}
Params withWaitForActiveShards(ActiveShardCount activeShardCount) { Params withWaitForActiveShards(ActiveShardCount activeShardCount) {
return withWaitForActiveShards(activeShardCount, ActiveShardCount.DEFAULT); return withWaitForActiveShards(activeShardCount, ActiveShardCount.DEFAULT);
} }

View File

@ -69,7 +69,10 @@ final class WatcherRequestConverters {
.build(); .build();
Request request = new Request(HttpPut.METHOD_NAME, endpoint); Request request = new Request(HttpPut.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request).withVersion(putWatchRequest.getVersion()); RequestConverters.Params params = new RequestConverters.Params(request)
.withVersion(putWatchRequest.getVersion())
.withIfSeqNo(putWatchRequest.ifSeqNo())
.withIfPrimaryTerm(putWatchRequest.ifPrimaryTerm());
if (putWatchRequest.isActive() == false) { if (putWatchRequest.isActive() == false) {
params.putParam("active", "false"); params.putParam("active", "false");
} }

View File

@ -31,9 +31,14 @@ import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
public class GetWatchResponse { public class GetWatchResponse {
private final String id; private final String id;
private final long version; private final long version;
private final long seqNo;
private final long primaryTerm;
private final WatchStatus status; private final WatchStatus status;
private final BytesReference source; private final BytesReference source;
@ -43,15 +48,18 @@ public class GetWatchResponse {
* Ctor for missing watch * Ctor for missing watch
*/ */
public GetWatchResponse(String id) { public GetWatchResponse(String id) {
this(id, Versions.NOT_FOUND, null, null, null); this(id, Versions.NOT_FOUND, UNASSIGNED_SEQ_NO, UNASSIGNED_PRIMARY_TERM, null, null, null);
} }
public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source, XContentType xContentType) { public GetWatchResponse(String id, long version, long seqNo, long primaryTerm, WatchStatus status,
BytesReference source, XContentType xContentType) {
this.id = id; this.id = id;
this.version = version; this.version = version;
this.status = status; this.status = status;
this.source = source; this.source = source;
this.xContentType = xContentType; this.xContentType = xContentType;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
} }
public String getId() { public String getId() {
@ -62,6 +70,14 @@ public class GetWatchResponse {
return version; return version;
} }
public long getSeqNo() {
return seqNo;
}
public long getPrimaryTerm() {
return primaryTerm;
}
public boolean isFound() { public boolean isFound() {
return version != Versions.NOT_FOUND; return version != Versions.NOT_FOUND;
} }
@ -111,6 +127,8 @@ public class GetWatchResponse {
private static final ParseField ID_FIELD = new ParseField("_id"); private static final ParseField ID_FIELD = new ParseField("_id");
private static final ParseField FOUND_FIELD = new ParseField("found"); private static final ParseField FOUND_FIELD = new ParseField("found");
private static final ParseField VERSION_FIELD = new ParseField("_version"); private static final ParseField VERSION_FIELD = new ParseField("_version");
private static final ParseField SEQ_NO_FIELD = new ParseField("_seq_no");
private static final ParseField PRIMARY_TERM_FIELD = new ParseField("_primary_term");
private static final ParseField STATUS_FIELD = new ParseField("status"); private static final ParseField STATUS_FIELD = new ParseField("status");
private static final ParseField WATCH_FIELD = new ParseField("watch"); private static final ParseField WATCH_FIELD = new ParseField("watch");
@ -119,9 +137,10 @@ public class GetWatchResponse {
a -> { a -> {
boolean isFound = (boolean) a[1]; boolean isFound = (boolean) a[1];
if (isFound) { if (isFound) {
XContentBuilder builder = (XContentBuilder) a[4]; XContentBuilder builder = (XContentBuilder) a[6];
BytesReference source = BytesReference.bytes(builder); BytesReference source = BytesReference.bytes(builder);
return new GetWatchResponse((String) a[0], (long) a[2], (WatchStatus) a[3], source, builder.contentType()); return new GetWatchResponse((String) a[0], (long) a[2], (long) a[3], (long) a[4], (WatchStatus) a[5],
source, builder.contentType());
} else { } else {
return new GetWatchResponse((String) a[0]); return new GetWatchResponse((String) a[0]);
} }
@ -131,6 +150,8 @@ public class GetWatchResponse {
PARSER.declareString(ConstructingObjectParser.constructorArg(), ID_FIELD); PARSER.declareString(ConstructingObjectParser.constructorArg(), ID_FIELD);
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), FOUND_FIELD); PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), FOUND_FIELD);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION_FIELD); PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION_FIELD);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), SEQ_NO_FIELD);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), PRIMARY_TERM_FIELD);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(),
(parser, context) -> WatchStatus.parse(parser), STATUS_FIELD); (parser, context) -> WatchStatus.parse(parser), STATUS_FIELD);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(),

View File

@ -23,10 +23,14 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.seqno.SequenceNumbers;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
/** /**
* This request class contains the data needed to create a watch along with the name of the watch. * This request class contains the data needed to create a watch along with the name of the watch.
* The name of the watch will become the ID of the indexed document. * The name of the watch will become the ID of the indexed document.
@ -40,6 +44,9 @@ public final class PutWatchRequest implements Validatable {
private final XContentType xContentType; private final XContentType xContentType;
private boolean active = true; private boolean active = true;
private long version = Versions.MATCH_ANY; private long version = Versions.MATCH_ANY;
private long ifSeqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
private long ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM;
public PutWatchRequest(String id, BytesReference source, XContentType xContentType) { public PutWatchRequest(String id, BytesReference source, XContentType xContentType) {
Objects.requireNonNull(id, "watch id is missing"); Objects.requireNonNull(id, "watch id is missing");
@ -96,6 +103,56 @@ public final class PutWatchRequest implements Validatable {
this.version = version; this.version = version;
} }
/**
* only performs this put request if the watch's last modification was assigned the given
* sequence number. Must be used in combination with {@link #setIfPrimaryTerm(long)}
*
* If the watch's last modification was assigned a different sequence number a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public PutWatchRequest setIfSeqNo(long seqNo) {
if (seqNo < 0 && seqNo != UNASSIGNED_SEQ_NO) {
throw new IllegalArgumentException("sequence numbers must be non negative. got [" + seqNo + "].");
}
ifSeqNo = seqNo;
return this;
}
/**
* only performs this put request if the watch's last modification was assigned the given
* primary term. Must be used in combination with {@link #setIfSeqNo(long)}
*
* If the watch last modification was assigned a different term a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public PutWatchRequest setIfPrimaryTerm(long term) {
if (term < 0) {
throw new IllegalArgumentException("primary term must be non negative. got [" + term + "]");
}
ifPrimaryTerm = term;
return this;
}
/**
* If set, only perform this put watch request if the watch's last modification was assigned this sequence number.
* If the watch last last modification was assigned a different sequence number a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public long ifSeqNo() {
return ifSeqNo;
}
/**
* If set, only perform this put watch request if the watch's last modification was assigned this primary term.
*
* If the watch's last modification was assigned a different term a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public long ifPrimaryTerm() {
return ifPrimaryTerm;
}
public static boolean isValidId(String id) { public static boolean isValidId(String id) {
return Strings.isEmpty(id) == false && NO_WS_PATTERN.matcher(id).matches(); return Strings.isEmpty(id) == false && NO_WS_PATTERN.matcher(id).matches();
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.client.watcher;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.seqno.SequenceNumbers;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@ -32,20 +33,26 @@ public class PutWatchResponse {
static { static {
PARSER.declareString(PutWatchResponse::setId, new ParseField("_id")); PARSER.declareString(PutWatchResponse::setId, new ParseField("_id"));
PARSER.declareLong(PutWatchResponse::setSeqNo, new ParseField("_seq_no"));
PARSER.declareLong(PutWatchResponse::setPrimaryTerm, new ParseField("_primary_term"));
PARSER.declareLong(PutWatchResponse::setVersion, new ParseField("_version")); PARSER.declareLong(PutWatchResponse::setVersion, new ParseField("_version"));
PARSER.declareBoolean(PutWatchResponse::setCreated, new ParseField("created")); PARSER.declareBoolean(PutWatchResponse::setCreated, new ParseField("created"));
} }
private String id; private String id;
private long version; private long version;
private long seqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
private long primaryTerm = SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
private boolean created; private boolean created;
public PutWatchResponse() { public PutWatchResponse() {
} }
public PutWatchResponse(String id, long version, boolean created) { public PutWatchResponse(String id, long version, long seqNo, long primaryTerm, boolean created) {
this.id = id; this.id = id;
this.version = version; this.version = version;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
this.created = created; this.created = created;
} }
@ -57,6 +64,14 @@ public class PutWatchResponse {
this.version = version; this.version = version;
} }
private void setSeqNo(long seqNo) {
this.seqNo = seqNo;
}
private void setPrimaryTerm(long primaryTerm) {
this.primaryTerm = primaryTerm;
}
private void setCreated(boolean created) { private void setCreated(boolean created) {
this.created = created; this.created = created;
} }
@ -69,6 +84,14 @@ public class PutWatchResponse {
return version; return version;
} }
public long getSeqNo() {
return seqNo;
}
public long getPrimaryTerm() {
return primaryTerm;
}
public boolean isCreated() { public boolean isCreated() {
return created; return created;
} }
@ -80,12 +103,14 @@ public class PutWatchResponse {
PutWatchResponse that = (PutWatchResponse) o; PutWatchResponse that = (PutWatchResponse) o;
return Objects.equals(id, that.id) && Objects.equals(version, that.version) && Objects.equals(created, that.created); return Objects.equals(id, that.id) && Objects.equals(version, that.version)
&& Objects.equals(seqNo, that.seqNo)
&& Objects.equals(primaryTerm, that.primaryTerm) && Objects.equals(created, that.created);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, version, created); return Objects.hash(id, version, seqNo, primaryTerm, created);
} }
public static PutWatchResponse fromXContent(XContentParser parser) throws IOException { public static PutWatchResponse fromXContent(XContentParser parser) throws IOException {

View File

@ -41,14 +41,18 @@ public class PutWatchResponseTests extends ESTestCase {
return builder.startObject() return builder.startObject()
.field("_id", response.getId()) .field("_id", response.getId())
.field("_version", response.getVersion()) .field("_version", response.getVersion())
.field("_seq_no", response.getSeqNo())
.field("_primary_term", response.getPrimaryTerm())
.field("created", response.isCreated()) .field("created", response.isCreated())
.endObject(); .endObject();
} }
private static PutWatchResponse createTestInstance() { private static PutWatchResponse createTestInstance() {
String id = randomAlphaOfLength(10); String id = randomAlphaOfLength(10);
long seqNo = randomNonNegativeLong();
long primaryTerm = randomLongBetween(1, 200);
long version = randomLongBetween(1, 10); long version = randomLongBetween(1, 10);
boolean created = randomBoolean(); boolean created = randomBoolean();
return new PutWatchResponse(id, version, created); return new PutWatchResponse(id, version, seqNo, primaryTerm, created);
} }
} }

View File

@ -92,6 +92,8 @@ The action state of a newly-created watch is `awaits_successful_execution`:
-------------------------------------------------- --------------------------------------------------
{ {
"found": true, "found": true,
"_seq_no": 0,
"_primary_term": 1,
"_version": 1, "_version": 1,
"_id": "my_watch", "_id": "my_watch",
"status": { "status": {
@ -136,6 +138,8 @@ and the action is now in `ackable` state:
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_seq_no": 1,
"_primary_term": 1,
"_version": 2, "_version": 2,
"status": { "status": {
"version": 2, "version": 2,
@ -185,6 +189,8 @@ GET _watcher/watch/my_watch
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_seq_no": 2,
"_primary_term": 1,
"_version": 3, "_version": 3,
"status": { "status": {
"version": 3, "version": 3,

View File

@ -44,6 +44,8 @@ GET _watcher/watch/my_watch
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_seq_no": 0,
"_primary_term": 1,
"_version": 1, "_version": 1,
"status": { "status": {
"state" : { "state" : {

View File

@ -44,6 +44,8 @@ GET _watcher/watch/my_watch
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_version": 1, "_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"status": { "status": {
"state" : { "state" : {
"active" : true, "active" : true,

View File

@ -44,6 +44,8 @@ Response:
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_seq_no": 0,
"_primary_term": 1,
"_version": 1, "_version": 1,
"status": { <1> "status": { <1>
"version": 1, "version": 1,

View File

@ -5,19 +5,24 @@
*/ */
package org.elasticsearch.protocol.xpack.watcher; package org.elasticsearch.protocol.xpack.watcher;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ValidateActions;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.seqno.SequenceNumbers;
import java.io.IOException; import java.io.IOException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
/** /**
* This request class contains the data needed to create a watch along with the name of the watch. * This request class contains the data needed to create a watch along with the name of the watch.
* The name of the watch will become the ID of the indexed document. * The name of the watch will become the ID of the indexed document.
@ -32,6 +37,9 @@ public final class PutWatchRequest extends ActionRequest {
private boolean active = true; private boolean active = true;
private long version = Versions.MATCH_ANY; private long version = Versions.MATCH_ANY;
private long ifSeqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
private long ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM;
public PutWatchRequest() {} public PutWatchRequest() {}
public PutWatchRequest(StreamInput in) throws IOException { public PutWatchRequest(StreamInput in) throws IOException {
@ -52,6 +60,13 @@ public final class PutWatchRequest extends ActionRequest {
active = in.readBoolean(); active = in.readBoolean();
xContentType = in.readEnum(XContentType.class); xContentType = in.readEnum(XContentType.class);
version = in.readZLong(); version = in.readZLong();
if (in.getVersion().onOrAfter(Version.V_7_0_0)) {
ifSeqNo = in.readZLong();
ifPrimaryTerm = in.readVLong();
} else {
ifSeqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM;
}
} }
@Override @Override
@ -62,6 +77,10 @@ public final class PutWatchRequest extends ActionRequest {
out.writeBoolean(active); out.writeBoolean(active);
out.writeEnum(xContentType); out.writeEnum(xContentType);
out.writeZLong(version); out.writeZLong(version);
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
out.writeZLong(ifSeqNo);
out.writeVLong(ifPrimaryTerm);
}
} }
/** /**
@ -122,20 +141,80 @@ public final class PutWatchRequest extends ActionRequest {
this.version = version; this.version = version;
} }
/**
* only performs this put request if the watch's last modification was assigned the given
* sequence number. Must be used in combination with {@link #setIfPrimaryTerm(long)}
*
* If the watch's last modification was assigned a different sequence number a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public PutWatchRequest setIfSeqNo(long seqNo) {
if (seqNo < 0 && seqNo != UNASSIGNED_SEQ_NO) {
throw new IllegalArgumentException("sequence numbers must be non negative. got [" + seqNo + "].");
}
ifSeqNo = seqNo;
return this;
}
/**
* only performs this put request if the watch's last modification was assigned the given
* primary term. Must be used in combination with {@link #setIfSeqNo(long)}
*
* If the watch last modification was assigned a different term a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public PutWatchRequest setIfPrimaryTerm(long term) {
if (term < 0) {
throw new IllegalArgumentException("primary term must be non negative. got [" + term + "]");
}
ifPrimaryTerm = term;
return this;
}
/**
* If set, only perform this put watch request if the watch's last modification was assigned this sequence number.
* If the watch last last modification was assigned a different sequence number a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public long getIfSeqNo() {
return ifSeqNo;
}
/**
* If set, only perform this put watch request if the watch's last modification was assigned this primary term.
*
* If the watch's last modification was assigned a different term a
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown.
*/
public long getIfPrimaryTerm() {
return ifPrimaryTerm;
}
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; ActionRequestValidationException validationException = null;
if (id == null) { if (id == null) {
validationException = ValidateActions.addValidationError("watch id is missing", validationException); validationException = addValidationError("watch id is missing", validationException);
} else if (isValidId(id) == false) { } else if (isValidId(id) == false) {
validationException = ValidateActions.addValidationError("watch id contains whitespace", validationException); validationException = addValidationError("watch id contains whitespace", validationException);
} }
if (source == null) { if (source == null) {
validationException = ValidateActions.addValidationError("watch source is missing", validationException); validationException = addValidationError("watch source is missing", validationException);
} }
if (xContentType == null) { if (xContentType == null) {
validationException = ValidateActions.addValidationError("request body is missing", validationException); validationException = addValidationError("request body is missing", validationException);
} }
if (ifSeqNo != UNASSIGNED_SEQ_NO && version != Versions.MATCH_ANY) {
validationException = addValidationError("compare and write operations can not use versioning", validationException);
}
if (ifPrimaryTerm == UNASSIGNED_PRIMARY_TERM && ifSeqNo != UNASSIGNED_SEQ_NO) {
validationException = addValidationError("ifSeqNo is set, but primary term is [0]", validationException);
}
if (ifPrimaryTerm != UNASSIGNED_PRIMARY_TERM && ifSeqNo == UNASSIGNED_SEQ_NO) {
validationException =
addValidationError("ifSeqNo is unassigned, but primary term is [" + ifPrimaryTerm + "]", validationException);
}
return validationException; return validationException;
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.protocol.xpack.watcher; package org.elasticsearch.protocol.xpack.watcher;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -13,6 +14,7 @@ import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.seqno.SequenceNumbers;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@ -24,19 +26,25 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
static { static {
PARSER.declareString(PutWatchResponse::setId, new ParseField("_id")); PARSER.declareString(PutWatchResponse::setId, new ParseField("_id"));
PARSER.declareLong(PutWatchResponse::setVersion, new ParseField("_version")); PARSER.declareLong(PutWatchResponse::setVersion, new ParseField("_version"));
PARSER.declareLong(PutWatchResponse::setSeqNo, new ParseField("_seq_no"));
PARSER.declareLong(PutWatchResponse::setPrimaryTerm, new ParseField("_primary_term"));
PARSER.declareBoolean(PutWatchResponse::setCreated, new ParseField("created")); PARSER.declareBoolean(PutWatchResponse::setCreated, new ParseField("created"));
} }
private String id; private String id;
private long version; private long version;
private long seqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
private long primaryTerm = SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
private boolean created; private boolean created;
public PutWatchResponse() { public PutWatchResponse() {
} }
public PutWatchResponse(String id, long version, boolean created) { public PutWatchResponse(String id, long version, long seqNo, long primaryTerm, boolean created) {
this.id = id; this.id = id;
this.version = version; this.version = version;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
this.created = created; this.created = created;
} }
@ -48,6 +56,14 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
this.version = version; this.version = version;
} }
private void setSeqNo(long seqNo) {
this.seqNo = seqNo;
}
private void setPrimaryTerm(long primaryTerm) {
this.primaryTerm = primaryTerm;
}
private void setCreated(boolean created) { private void setCreated(boolean created) {
this.created = created; this.created = created;
} }
@ -60,6 +76,14 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
return version; return version;
} }
public long getSeqNo() {
return seqNo;
}
public long getPrimaryTerm() {
return primaryTerm;
}
public boolean isCreated() { public boolean isCreated() {
return created; return created;
} }
@ -71,12 +95,14 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
PutWatchResponse that = (PutWatchResponse) o; PutWatchResponse that = (PutWatchResponse) o;
return Objects.equals(id, that.id) && Objects.equals(version, that.version) && Objects.equals(created, that.created); return Objects.equals(id, that.id) && Objects.equals(version, that.version)
&& Objects.equals(seqNo, that.seqNo)
&& Objects.equals(primaryTerm, that.primaryTerm) && Objects.equals(created, that.created);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, version, created); return Objects.hash(id, version, seqNo, primaryTerm, created);
} }
@Override @Override
@ -84,6 +110,10 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
super.writeTo(out); super.writeTo(out);
out.writeString(id); out.writeString(id);
out.writeVLong(version); out.writeVLong(version);
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
out.writeZLong(seqNo);
out.writeVLong(primaryTerm);
}
out.writeBoolean(created); out.writeBoolean(created);
} }
@ -92,6 +122,13 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
super.readFrom(in); super.readFrom(in);
id = in.readString(); id = in.readString();
version = in.readVLong(); version = in.readVLong();
if (in.getVersion().onOrAfter(Version.V_7_0_0)) {
seqNo = in.readZLong();
primaryTerm = in.readVLong();
} else {
seqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
primaryTerm = SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
}
created = in.readBoolean(); created = in.readBoolean();
} }
@ -100,6 +137,8 @@ public class PutWatchResponse extends ActionResponse implements ToXContentObject
return builder.startObject() return builder.startObject()
.field("_id", id) .field("_id", id)
.field("_version", version) .field("_version", version)
.field("_seq_no", seqNo)
.field("_primary_term", primaryTerm)
.field("created", created) .field("created", created)
.endObject(); .endObject();
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.watcher.transport.actions.get; package org.elasticsearch.xpack.core.watcher.transport.actions.get;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -12,6 +13,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource;
import org.elasticsearch.xpack.core.watcher.watch.WatchStatus; import org.elasticsearch.xpack.core.watcher.watch.WatchStatus;
@ -25,6 +27,8 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
private boolean found; private boolean found;
private XContentSource source; private XContentSource source;
private long version; private long version;
private long seqNo;
private long primaryTerm;
public GetWatchResponse() { public GetWatchResponse() {
} }
@ -38,17 +42,21 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
this.found = false; this.found = false;
this.source = null; this.source = null;
this.version = Versions.NOT_FOUND; this.version = Versions.NOT_FOUND;
this.seqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
this.primaryTerm = SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
} }
/** /**
* ctor for found watch * ctor for found watch
*/ */
public GetWatchResponse(String id, long version, WatchStatus status, XContentSource source) { public GetWatchResponse(String id, long version, long seqNo, long primaryTerm, WatchStatus status, XContentSource source) {
this.id = id; this.id = id;
this.status = status; this.status = status;
this.found = true; this.found = true;
this.source = source; this.source = source;
this.version = version; this.version = version;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
} }
public String getId() { public String getId() {
@ -71,6 +79,14 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
return version; return version;
} }
public long getSeqNo() {
return seqNo;
}
public long getPrimaryTerm() {
return primaryTerm;
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
@ -80,10 +96,16 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
status = WatchStatus.read(in); status = WatchStatus.read(in);
source = XContentSource.readFrom(in); source = XContentSource.readFrom(in);
version = in.readZLong(); version = in.readZLong();
if (in.getVersion().onOrAfter(Version.V_7_0_0)) {
seqNo = in.readZLong();
primaryTerm = in.readVLong();
}
} else { } else {
status = null; status = null;
source = null; source = null;
version = Versions.NOT_FOUND; version = Versions.NOT_FOUND;
seqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
primaryTerm = SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
} }
} }
@ -96,6 +118,10 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
status.writeTo(out); status.writeTo(out);
XContentSource.writeTo(source, out); XContentSource.writeTo(source, out);
out.writeZLong(version); out.writeZLong(version);
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
out.writeZLong(seqNo);
out.writeVLong(primaryTerm);
}
} }
} }
@ -105,6 +131,8 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
builder.field("_id", id); builder.field("_id", id);
if (found) { if (found) {
builder.field("_version", version); builder.field("_version", version);
builder.field("_seq_no", seqNo);
builder.field("_primary_term", primaryTerm);
builder.field("status", status, params); builder.field("status", status, params);
builder.field("watch", source, params); builder.field("watch", source, params);
} }
@ -116,7 +144,7 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
GetWatchResponse that = (GetWatchResponse) o; GetWatchResponse that = (GetWatchResponse) o;
return version == that.version && return version == that.version && seqNo == that.seqNo && primaryTerm == that.primaryTerm &&
Objects.equals(id, that.id) && Objects.equals(id, that.id) &&
Objects.equals(status, that.status) && Objects.equals(status, that.status) &&
Objects.equals(source, that.source); Objects.equals(source, that.source);
@ -124,7 +152,7 @@ public class GetWatchResponse extends ActionResponse implements ToXContent {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, status, version); return Objects.hash(id, status, version, seqNo, primaryTerm);
} }
@Override @Override

View File

@ -9,6 +9,7 @@ import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.xpack.core.watcher.actions.ActionStatus; import org.elasticsearch.xpack.core.watcher.actions.ActionStatus;
import org.elasticsearch.xpack.core.watcher.actions.ActionWrapper; import org.elasticsearch.xpack.core.watcher.actions.ActionWrapper;
import org.elasticsearch.xpack.core.watcher.condition.ExecutableCondition; import org.elasticsearch.xpack.core.watcher.condition.ExecutableCondition;
@ -37,11 +38,12 @@ public class Watch implements ToXContentObject {
@Nullable private final Map<String, Object> metadata; @Nullable private final Map<String, Object> metadata;
private final WatchStatus status; private final WatchStatus status;
private transient long version; private final long sourceSeqNo;
private final long sourcePrimaryTerm;
public Watch(String id, Trigger trigger, ExecutableInput input, ExecutableCondition condition, @Nullable ExecutableTransform transform, public Watch(String id, Trigger trigger, ExecutableInput input, ExecutableCondition condition, @Nullable ExecutableTransform transform,
@Nullable TimeValue throttlePeriod, List<ActionWrapper> actions, @Nullable Map<String, Object> metadata, @Nullable TimeValue throttlePeriod, List<ActionWrapper> actions, @Nullable Map<String, Object> metadata,
WatchStatus status, long version) { WatchStatus status, long sourceSeqNo, long sourcePrimaryTerm) {
this.id = id; this.id = id;
this.trigger = trigger; this.trigger = trigger;
this.input = input; this.input = input;
@ -51,7 +53,8 @@ public class Watch implements ToXContentObject {
this.throttlePeriod = throttlePeriod; this.throttlePeriod = throttlePeriod;
this.metadata = metadata; this.metadata = metadata;
this.status = status; this.status = status;
this.version = version; this.sourceSeqNo = sourceSeqNo;
this.sourcePrimaryTerm = sourcePrimaryTerm;
} }
public String id() { public String id() {
@ -88,12 +91,20 @@ public class Watch implements ToXContentObject {
return status; return status;
} }
public long version() { /**
return version; * The sequence number of the document that was used to create this watch, {@link SequenceNumbers#UNASSIGNED_SEQ_NO}
* if the watch wasn't read from a document
***/
public long getSourceSeqNo() {
return sourceSeqNo;
} }
public void version(long version) { /**
this.version = version; * The primary term of the document that was used to create this watch, {@link SequenceNumbers#UNASSIGNED_PRIMARY_TERM}
* if the watch wasn't read from a document
***/
public long getSourcePrimaryTerm() {
return sourcePrimaryTerm;
} }
/** /**

View File

@ -17,7 +17,6 @@ public final class WatchField {
public static final ParseField THROTTLE_PERIOD_HUMAN = new ParseField("throttle_period"); public static final ParseField THROTTLE_PERIOD_HUMAN = new ParseField("throttle_period");
public static final ParseField METADATA = new ParseField("metadata"); public static final ParseField METADATA = new ParseField("metadata");
public static final ParseField STATUS = new ParseField("status"); public static final ParseField STATUS = new ParseField("status");
public static final ParseField VERSION = new ParseField("_version");
public static final String ALL_ACTIONS_ID = "_all"; public static final String ALL_ACTIONS_ID = "_all";
private WatchField() {} private WatchField() {}

View File

@ -74,6 +74,7 @@ public class GetWatchResponseTests extends
throw new AssertionError(e); throw new AssertionError(e);
} }
newInstance = new GetWatchResponse(newInstance.getId(), newInstance.getVersion(), newInstance = new GetWatchResponse(newInstance.getId(), newInstance.getVersion(),
newInstance.getSeqNo(), newInstance.getPrimaryTerm(),
newInstance.getStatus(), new XContentSource(newSource, expectedInstance.getSource().getContentType())); newInstance.getStatus(), new XContentSource(newSource, expectedInstance.getSource().getContentType()));
} }
super.assertEqualInstances(expectedInstance, newInstance); super.assertEqualInstances(expectedInstance, newInstance);
@ -91,9 +92,11 @@ public class GetWatchResponseTests extends
return new GetWatchResponse(id); return new GetWatchResponse(id);
} }
long version = randomLongBetween(0, 10); long version = randomLongBetween(0, 10);
long seqNo = randomNonNegativeLong();
long primaryTerm = randomLongBetween(1, 2000);
WatchStatus status = randomWatchStatus(); WatchStatus status = randomWatchStatus();
BytesReference source = simpleWatch(); BytesReference source = simpleWatch();
return new GetWatchResponse(id, version, status, new XContentSource(source, XContentType.JSON)); return new GetWatchResponse(id, version, seqNo, primaryTerm, status, new XContentSource(source, XContentType.JSON));
} }
private static BytesReference simpleWatch() { private static BytesReference simpleWatch() {
@ -170,8 +173,8 @@ public class GetWatchResponseTests extends
@Override @Override
public GetWatchResponse convertHlrcToInternal(org.elasticsearch.client.watcher.GetWatchResponse instance) { public GetWatchResponse convertHlrcToInternal(org.elasticsearch.client.watcher.GetWatchResponse instance) {
if (instance.isFound()) { if (instance.isFound()) {
return new GetWatchResponse(instance.getId(), instance.getVersion(), convertHlrcToInternal(instance.getStatus()), return new GetWatchResponse(instance.getId(), instance.getVersion(), instance.getSeqNo(), instance.getPrimaryTerm(),
new XContentSource(instance.getSource(), instance.getContentType())); convertHlrcToInternal(instance.getStatus()), new XContentSource(instance.getSource(), instance.getContentType()));
} else { } else {
return new GetWatchResponse(instance.getId()); return new GetWatchResponse(instance.getId());
} }

View File

@ -16,9 +16,11 @@ public class PutWatchResponseTests extends
@Override @Override
protected PutWatchResponse createTestInstance() { protected PutWatchResponse createTestInstance() {
String id = randomAlphaOfLength(10); String id = randomAlphaOfLength(10);
long seqNo = randomNonNegativeLong();
long primaryTerm = randomLongBetween(1, 20);
long version = randomLongBetween(1, 10); long version = randomLongBetween(1, 10);
boolean created = randomBoolean(); boolean created = randomBoolean();
return new PutWatchResponse(id, version, created); return new PutWatchResponse(id, version, seqNo, primaryTerm, created);
} }
@Override @Override
@ -33,7 +35,8 @@ public class PutWatchResponseTests extends
@Override @Override
public PutWatchResponse convertHlrcToInternal(org.elasticsearch.client.watcher.PutWatchResponse instance) { public PutWatchResponse convertHlrcToInternal(org.elasticsearch.client.watcher.PutWatchResponse instance) {
return new PutWatchResponse(instance.getId(), instance.getVersion(), instance.isCreated()); return new PutWatchResponse(instance.getId(), instance.getVersion(), instance.getSeqNo(), instance.getPrimaryTerm(),
instance.isCreated());
} }
@Override @Override

View File

@ -20,6 +20,14 @@
"version" : { "version" : {
"type" : "number", "type" : "number",
"description" : "Explicit version number for concurrency control" "description" : "Explicit version number for concurrency control"
},
"if_seq_no" : {
"type" : "number",
"description" : "only update the watch if the last operation that has changed the watch has the specified sequence number"
},
"if_primary_term" : {
"type" : "number",
"description" : "only update the watch if the last operation that has changed the watch has the specified primary term"
} }
} }
}, },

View File

@ -221,6 +221,175 @@ setup:
} }
} }
---
"Test putting a watch with a redacted password with old seq no returns an error":
- skip:
version: " - 6.99.99"
reason: seq no powered concurrency was added in 7.0.0
# version 1
- do:
xpack.watcher.put_watch:
id: "watch_with_seq_no"
body: >
{
"trigger": {
"schedule" : { "cron" : "0 0 0 1 * ? 2099" }
},
"input": {
"http" : {
"request" : {
"host" : "host.domain",
"port" : 9200,
"path" : "/myservice",
"auth" : {
"basic" : {
"username" : "user",
"password" : "pass"
}
}
}
}
},
"actions": {
"logging": {
"logging": {
"text": "Log me Amadeus!"
}
}
}
}
- set: { "_seq_no": seqNo }
- set: { "_primary_term" : primaryTerm }
# using optimistic concurrency control, this one will loose
# as if two users in the watch UI tried to update the same watch
- do:
catch: conflict
xpack.watcher.put_watch:
id: "watch_with_seq_no"
if_seq_no: 123034
if_primary_term: $primaryTerm
body: >
{
"trigger": {
"schedule" : { "cron" : "0 0 0 1 * ? 2099" }
},
"input": {
"http" : {
"request" : {
"host" : "host.domain",
"port" : 9200,
"path" : "/myservice",
"auth" : {
"basic" : {
"username" : "user",
"password" : "::es_redacted::"
}
}
}
}
},
"actions": {
"logging": {
"logging": {
"text": "Log me Amadeus!"
}
}
}
}
- do:
catch: conflict
xpack.watcher.put_watch:
id: "watch_with_seq_no"
if_seq_no: $seqNo
if_primary_term: 234242423
body: >
{
"trigger": {
"schedule" : { "cron" : "0 0 0 1 * ? 2099" }
},
"input": {
"http" : {
"request" : {
"host" : "host.domain",
"port" : 9200,
"path" : "/myservice",
"auth" : {
"basic" : {
"username" : "user",
"password" : "::es_redacted::"
}
}
}
}
},
"actions": {
"logging": {
"logging": {
"text": "Log me Amadeus!"
}
}
}
}
- do:
xpack.watcher.put_watch:
id: "watch_with_seq_no"
if_seq_no: $seqNo
if_primary_term: $primaryTerm
body: >
{
"trigger": {
"schedule" : { "cron" : "0 0 0 1 * ? 2099" }
},
"input": {
"http" : {
"request" : {
"host" : "host.domain",
"port" : 9200,
"path" : "/myservice",
"auth" : {
"basic" : {
"username" : "new_user",
"password" : "::es_redacted::"
}
}
}
}
},
"actions": {
"logging": {
"logging": {
"text": "Log me Amadeus!"
}
}
}
}
- do:
search:
rest_total_hits_as_int: true
index: .watches
body: >
{
"query": {
"term": {
"_id": {
"value": "watch_with_seq_no"
}
}
}
}
- match: { hits.total: 1 }
- match: { hits.hits.0._id: "watch_with_seq_no" }
- match: { hits.hits.0._source.input.http.request.auth.basic.username: "new_user" }
- match: { hits.hits.0._source.input.http.request.auth.basic.password: "pass" }
--- ---
"Test putting a watch with a redacted password with current version works": "Test putting a watch with a redacted password with current version works":

View File

@ -103,7 +103,8 @@ final class WatcherIndexingListener implements IndexingOperationListener, Cluste
if (isWatchDocument(shardId.getIndexName(), operation.type())) { if (isWatchDocument(shardId.getIndexName(), operation.type())) {
DateTime now = new DateTime(clock.millis(), UTC); DateTime now = new DateTime(clock.millis(), UTC);
try { try {
Watch watch = parser.parseWithSecrets(operation.id(), true, operation.source(), now, XContentType.JSON); Watch watch = parser.parseWithSecrets(operation.id(), true, operation.source(), now, XContentType.JSON,
operation.getIfSeqNo(), operation.getIfPrimaryTerm());
ShardAllocationConfiguration shardAllocationConfiguration = configuration.localShards.get(shardId); ShardAllocationConfiguration shardAllocationConfiguration = configuration.localShards.get(shardId);
if (shardAllocationConfiguration == null) { if (shardAllocationConfiguration == null) {
logger.debug("no distributed watch execution info found for watch [{}] on shard [{}], got configuration for {}", logger.debug("no distributed watch execution info found for watch [{}] on shard [{}], got configuration for {}",

View File

@ -298,7 +298,7 @@ public class WatcherService {
.source(new SearchSourceBuilder() .source(new SearchSourceBuilder()
.size(scrollSize) .size(scrollSize)
.sort(SortBuilders.fieldSort("_doc")) .sort(SortBuilders.fieldSort("_doc"))
.version(true)); .seqNoAndPrimaryTerm(true));
response = client.search(searchRequest).actionGet(defaultSearchTimeout); response = client.search(searchRequest).actionGet(defaultSearchTimeout);
if (response.getTotalShards() != response.getSuccessfulShards()) { if (response.getTotalShards() != response.getSuccessfulShards()) {
@ -341,8 +341,7 @@ public class WatcherService {
} }
try { try {
Watch watch = parser.parse(id, true, hit.getSourceRef(), XContentType.JSON); Watch watch = parser.parse(id, true, hit.getSourceRef(), XContentType.JSON, hit.getSeqNo(), hit.getPrimaryTerm());
watch.version(hit.getVersion());
if (watch.status().state().isActive()) { if (watch.status().state().isActive()) {
watches.add(watch); watches.add(watch);
} }

View File

@ -282,7 +282,8 @@ public class ExecutionService {
if (resp.isExists() == false) { if (resp.isExists() == false) {
throw new ResourceNotFoundException("watch [{}] does not exist", watchId); throw new ResourceNotFoundException("watch [{}] does not exist", watchId);
} }
return parser.parseWithSecrets(watchId, true, resp.getSourceAsBytesRef(), ctx.executionTime(), XContentType.JSON); return parser.parseWithSecrets(watchId, true, resp.getSourceAsBytesRef(), ctx.executionTime(), XContentType.JSON,
resp.getSeqNo(), resp.getPrimaryTerm());
}); });
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
String message = "unable to find watch for record [" + ctx.id() + "]"; String message = "unable to find watch for record [" + ctx.id() + "]";
@ -353,7 +354,8 @@ public class ExecutionService {
UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, watch.id()); UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, watch.id());
updateRequest.doc(source); updateRequest.doc(source);
updateRequest.version(watch.version()); updateRequest.setIfSeqNo(watch.getSourceSeqNo());
updateRequest.setIfPrimaryTerm(watch.getSourcePrimaryTerm());
try (ThreadContext.StoredContext ignore = stashWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN)) { try (ThreadContext.StoredContext ignore = stashWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN)) {
client.update(updateRequest).actionGet(indexDefaultTimeout); client.update(updateRequest).actionGet(indexDefaultTimeout);
} catch (DocumentMissingException e) { } catch (DocumentMissingException e) {

View File

@ -50,8 +50,7 @@ public class RestExecuteWatchAction extends WatcherRestHandler implements RestRe
WatchField.INPUT.getPreferredName(), WatchField.CONDITION.getPreferredName(), WatchField.INPUT.getPreferredName(), WatchField.CONDITION.getPreferredName(),
WatchField.ACTIONS.getPreferredName(), WatchField.TRANSFORM.getPreferredName(), WatchField.ACTIONS.getPreferredName(), WatchField.TRANSFORM.getPreferredName(),
WatchField.THROTTLE_PERIOD.getPreferredName(), WatchField.THROTTLE_PERIOD_HUMAN.getPreferredName(), WatchField.THROTTLE_PERIOD.getPreferredName(), WatchField.THROTTLE_PERIOD_HUMAN.getPreferredName(),
WatchField.METADATA.getPreferredName(), WatchField.STATUS.getPreferredName(), WatchField.METADATA.getPreferredName(), WatchField.STATUS.getPreferredName());
WatchField.VERSION.getPreferredName());
public RestExecuteWatchAction(Settings settings, RestController controller) { public RestExecuteWatchAction(Settings settings, RestController controller) {
super(settings); super(settings);

View File

@ -9,7 +9,6 @@ package org.elasticsearch.xpack.watcher.rest.action;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
@ -22,7 +21,6 @@ import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchReques
import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchResponse; import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchResponse;
import org.elasticsearch.xpack.watcher.rest.WatcherRestHandler; import org.elasticsearch.xpack.watcher.rest.WatcherRestHandler;
import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.NOT_FOUND; import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
import static org.elasticsearch.rest.RestStatus.OK; import static org.elasticsearch.rest.RestStatus.OK;
@ -50,17 +48,9 @@ public class RestGetWatchAction extends WatcherRestHandler {
return channel -> client.getWatch(getWatchRequest, new RestBuilderListener<GetWatchResponse>(channel) { return channel -> client.getWatch(getWatchRequest, new RestBuilderListener<GetWatchResponse>(channel) {
@Override @Override
public RestResponse buildResponse(GetWatchResponse response, XContentBuilder builder) throws Exception { public RestResponse buildResponse(GetWatchResponse response, XContentBuilder builder) throws Exception {
builder.startObject() builder.startObject();
.field("found", response.isFound()) response.toXContent(builder, request);
.field("_id", response.getId()); builder.endObject();
if (response.isFound()) {
builder.field("_version", response.getVersion());
ToXContent.MapParams xContentParams = new ToXContent.MapParams(request.params());
builder.field("status", response.getStatus(), xContentParams);
builder.field("watch", response.getSource(), xContentParams);
}
builder.endObject();
RestStatus status = response.isFound() ? OK : NOT_FOUND; RestStatus status = response.isFound() ? OK : NOT_FOUND;
return new BytesRestResponse(status, builder); return new BytesRestResponse(status, builder);
} }

View File

@ -58,15 +58,13 @@ public class RestPutWatchAction extends WatcherRestHandler implements RestReques
PutWatchRequest putWatchRequest = PutWatchRequest putWatchRequest =
new PutWatchRequest(request.param("id"), request.content(), request.getXContentType()); new PutWatchRequest(request.param("id"), request.content(), request.getXContentType());
putWatchRequest.setVersion(request.paramAsLong("version", Versions.MATCH_ANY)); putWatchRequest.setVersion(request.paramAsLong("version", Versions.MATCH_ANY));
putWatchRequest.setIfSeqNo(request.paramAsLong("if_seq_no", putWatchRequest.getIfSeqNo()));
putWatchRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", putWatchRequest.getIfPrimaryTerm()));
putWatchRequest.setActive(request.paramAsBoolean("active", putWatchRequest.isActive())); putWatchRequest.setActive(request.paramAsBoolean("active", putWatchRequest.isActive()));
return channel -> client.putWatch(putWatchRequest, new RestBuilderListener<PutWatchResponse>(channel) { return channel -> client.putWatch(putWatchRequest, new RestBuilderListener<PutWatchResponse>(channel) {
@Override @Override
public RestResponse buildResponse(PutWatchResponse response, XContentBuilder builder) throws Exception { public RestResponse buildResponse(PutWatchResponse response, XContentBuilder builder) throws Exception {
builder.startObject() response.toXContent(builder, request);
.field("_id", response.getId())
.field("_version", response.getVersion())
.field("created", response.isCreated())
.endObject();
RestStatus status = response.isCreated() ? CREATED : OK; RestStatus status = response.isCreated() ? CREATED : OK;
return new BytesRestResponse(status, builder); return new BytesRestResponse(status, builder);
} }

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.watcher.transport.actions.ack;
import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.GetResponse;
@ -16,6 +17,7 @@ import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.routing.Preference; import org.elasticsearch.cluster.routing.Preference;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -49,15 +51,17 @@ public class TransportAckWatchAction extends WatcherTransportAction<AckWatchRequ
private final Clock clock; private final Clock clock;
private final WatchParser parser; private final WatchParser parser;
private final Client client; private final Client client;
private final ClusterService clusterService;
@Inject @Inject
public TransportAckWatchAction(TransportService transportService, ActionFilters actionFilters, public TransportAckWatchAction(TransportService transportService, ActionFilters actionFilters,
Clock clock, XPackLicenseState licenseState, WatchParser parser, Clock clock, XPackLicenseState licenseState, WatchParser parser,
Client client) { Client client, ClusterService clusterService) {
super(AckWatchAction.NAME, transportService, actionFilters, licenseState, AckWatchRequest::new); super(AckWatchAction.NAME, transportService, actionFilters, licenseState, AckWatchRequest::new);
this.clock = clock; this.clock = clock;
this.parser = parser; this.parser = parser;
this.client = client; this.client = client;
this.clusterService = clusterService;
} }
@Override @Override
@ -82,8 +86,7 @@ public class TransportAckWatchAction extends WatcherTransportAction<AckWatchRequ
} else { } else {
DateTime now = new DateTime(clock.millis(), UTC); DateTime now = new DateTime(clock.millis(), UTC);
Watch watch = parser.parseWithSecrets(request.getWatchId(), true, getResponse.getSourceAsBytesRef(), Watch watch = parser.parseWithSecrets(request.getWatchId(), true, getResponse.getSourceAsBytesRef(),
now, XContentType.JSON); now, XContentType.JSON, getResponse.getSeqNo(), getResponse.getPrimaryTerm());
watch.version(getResponse.getVersion());
watch.status().version(getResponse.getVersion()); watch.status().version(getResponse.getVersion());
String[] actionIds = request.getActionIds(); String[] actionIds = request.getActionIds();
if (actionIds == null || actionIds.length == 0) { if (actionIds == null || actionIds.length == 0) {
@ -99,7 +102,12 @@ public class TransportAckWatchAction extends WatcherTransportAction<AckWatchRequ
UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getWatchId()); UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getWatchId());
// this may reject this action, but prevents concurrent updates from a watch execution // this may reject this action, but prevents concurrent updates from a watch execution
updateRequest.version(getResponse.getVersion()); if (clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.V_7_0_0)) {
updateRequest.setIfSeqNo(getResponse.getSeqNo());
updateRequest.setIfPrimaryTerm(getResponse.getPrimaryTerm());
} else {
updateRequest.version(getResponse.getVersion());
}
updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
XContentBuilder builder = jsonBuilder(); XContentBuilder builder = jsonBuilder();
builder.startObject() builder.startObject()

View File

@ -79,8 +79,7 @@ public class TransportActivateWatchAction extends WatcherTransportAction<Activat
ActionListener.<GetResponse>wrap(getResponse -> { ActionListener.<GetResponse>wrap(getResponse -> {
if (getResponse.isExists()) { if (getResponse.isExists()) {
Watch watch = parser.parseWithSecrets(request.getWatchId(), true, getResponse.getSourceAsBytesRef(), now, Watch watch = parser.parseWithSecrets(request.getWatchId(), true, getResponse.getSourceAsBytesRef(), now,
XContentType.JSON); XContentType.JSON, getResponse.getSeqNo(), getResponse.getPrimaryTerm());
watch.version(getResponse.getVersion());
watch.status().version(getResponse.getVersion()); watch.status().version(getResponse.getVersion());
listener.onResponse(new ActivateWatchResponse(watch.status())); listener.onResponse(new ActivateWatchResponse(watch.status()));
} else { } else {

View File

@ -19,6 +19,7 @@ import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
@ -86,9 +87,8 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, getRequest, executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, getRequest,
ActionListener.<GetResponse>wrap(response -> { ActionListener.<GetResponse>wrap(response -> {
if (response.isExists()) { if (response.isExists()) {
Watch watch = Watch watch = watchParser.parse(request.getId(), true, response.getSourceAsBytesRef(),
watchParser.parse(request.getId(), true, response.getSourceAsBytesRef(), request.getXContentType()); request.getXContentType(), response.getSeqNo(), response.getPrimaryTerm());
watch.version(response.getVersion());
watch.status().version(response.getVersion()); watch.status().version(response.getVersion());
executeWatch(request, listener, watch, true); executeWatch(request, listener, watch, true);
} else { } else {
@ -99,7 +99,7 @@ public class TransportExecuteWatchAction extends WatcherTransportAction<ExecuteW
try { try {
assert !request.isRecordExecution(); assert !request.isRecordExecution();
Watch watch = watchParser.parse(ExecuteWatchRequest.INLINE_WATCH_ID, true, request.getWatchSource(), Watch watch = watchParser.parse(ExecuteWatchRequest.INLINE_WATCH_ID, true, request.getWatchSource(),
request.getXContentType()); request.getXContentType(), SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM);
executeWatch(request, listener, watch, false); executeWatch(request, listener, watch, false);
} catch (IOException e) { } catch (IOException e) {
logger.error(new ParameterizedMessage("failed to parse [{}]", request.getId()), e); logger.error(new ParameterizedMessage("failed to parse [{}]", request.getId()), e);

View File

@ -64,15 +64,15 @@ public class TransportGetWatchAction extends WatcherTransportAction<GetWatchRequ
// so that it indicates the the status is managed by watcher itself. // so that it indicates the the status is managed by watcher itself.
DateTime now = new DateTime(clock.millis(), UTC); DateTime now = new DateTime(clock.millis(), UTC);
Watch watch = parser.parseWithSecrets(request.getId(), true, getResponse.getSourceAsBytesRef(), now, Watch watch = parser.parseWithSecrets(request.getId(), true, getResponse.getSourceAsBytesRef(), now,
XContentType.JSON); XContentType.JSON, getResponse.getSeqNo(), getResponse.getPrimaryTerm());
watch.toXContent(builder, WatcherParams.builder() watch.toXContent(builder, WatcherParams.builder()
.hideSecrets(true) .hideSecrets(true)
.includeStatus(false) .includeStatus(false)
.build()); .build());
watch.version(getResponse.getVersion());
watch.status().version(getResponse.getVersion()); watch.status().version(getResponse.getVersion());
listener.onResponse(new GetWatchResponse(watch.id(), getResponse.getVersion(), watch.status(), listener.onResponse(new GetWatchResponse(watch.id(), getResponse.getVersion(),
new XContentSource(BytesReference.bytes(builder), XContentType.JSON))); watch.getSourceSeqNo(), watch.getSourcePrimaryTerm(),
watch.status(), new XContentSource(BytesReference.bytes(builder), XContentType.JSON)));
} }
} else { } else {
listener.onResponse(new GetWatchResponse(request.getId())); listener.onResponse(new GetWatchResponse(request.getId()));

View File

@ -17,6 +17,7 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest;
import org.elasticsearch.protocol.xpack.watcher.PutWatchResponse; import org.elasticsearch.protocol.xpack.watcher.PutWatchResponse;
@ -77,8 +78,9 @@ public class TransportPutWatchAction extends WatcherTransportAction<PutWatchRequ
protected void doExecute(PutWatchRequest request, ActionListener<PutWatchResponse> listener) { protected void doExecute(PutWatchRequest request, ActionListener<PutWatchResponse> listener) {
try { try {
DateTime now = new DateTime(clock.millis(), UTC); DateTime now = new DateTime(clock.millis(), UTC);
boolean isUpdate = request.getVersion() > 0; boolean isUpdate = request.getVersion() > 0 || request.getIfSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO;
Watch watch = parser.parseWithSecrets(request.getId(), false, request.getSource(), now, request.xContentType(), isUpdate); Watch watch = parser.parseWithSecrets(request.getId(), false, request.getSource(), now, request.xContentType(),
isUpdate, request.getIfSeqNo(), request.getIfPrimaryTerm());
watch.setState(request.isActive(), now); watch.setState(request.isActive(), now);
// ensure we only filter for the allowed headers // ensure we only filter for the allowed headers
@ -92,14 +94,20 @@ public class TransportPutWatchAction extends WatcherTransportAction<PutWatchRequ
if (isUpdate) { if (isUpdate) {
UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getId()); UpdateRequest updateRequest = new UpdateRequest(Watch.INDEX, Watch.DOC_TYPE, request.getId());
updateRequest.version(request.getVersion()); if (request.getIfSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO) {
updateRequest.setIfSeqNo(request.getIfSeqNo());
updateRequest.setIfPrimaryTerm(request.getIfPrimaryTerm());
} else {
updateRequest.version(request.getVersion());
}
updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
updateRequest.doc(builder); updateRequest.doc(builder);
executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, updateRequest, executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, updateRequest,
ActionListener.<UpdateResponse>wrap(response -> { ActionListener.<UpdateResponse>wrap(response -> {
boolean created = response.getResult() == DocWriteResponse.Result.CREATED; boolean created = response.getResult() == DocWriteResponse.Result.CREATED;
listener.onResponse(new PutWatchResponse(response.getId(), response.getVersion(), created)); listener.onResponse(new PutWatchResponse(response.getId(), response.getVersion(),
response.getSeqNo(), response.getPrimaryTerm(), created));
}, listener::onFailure), }, listener::onFailure),
client::update); client::update);
} else { } else {
@ -109,7 +117,8 @@ public class TransportPutWatchAction extends WatcherTransportAction<PutWatchRequ
executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, indexRequest, executeAsyncWithOrigin(client.threadPool().getThreadContext(), WATCHER_ORIGIN, indexRequest,
ActionListener.<IndexResponse>wrap(response -> { ActionListener.<IndexResponse>wrap(response -> {
boolean created = response.getResult() == DocWriteResponse.Result.CREATED; boolean created = response.getResult() == DocWriteResponse.Result.CREATED;
listener.onResponse(new PutWatchResponse(response.getId(), response.getVersion(), created)); listener.onResponse(new PutWatchResponse(response.getId(), response.getVersion(),
response.getSeqNo(), response.getPrimaryTerm(), created));
}, listener::onFailure), }, listener::onFailure),
client::index); client::index);
} }

View File

@ -10,7 +10,6 @@ import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -74,13 +73,15 @@ public class WatchParser {
this.defaultActions = Collections.emptyList(); this.defaultActions = Collections.emptyList();
} }
public Watch parse(String name, boolean includeStatus, BytesReference source, XContentType xContentType) throws IOException { public Watch parse(String name, boolean includeStatus, BytesReference source, XContentType xContentType,
return parse(name, includeStatus, false, source, new DateTime(clock.millis(), UTC), xContentType, false); long sourceSeqNo, long sourcePrimaryTerm) throws IOException {
return parse(name, includeStatus, false, source, new DateTime(clock.millis(), UTC), xContentType, false,
sourceSeqNo, sourcePrimaryTerm);
} }
public Watch parse(String name, boolean includeStatus, BytesReference source, DateTime now, public Watch parse(String name, boolean includeStatus, BytesReference source, DateTime now,
XContentType xContentType) throws IOException { XContentType xContentType, long sourceSeqNo, long sourcePrimaryTerm) throws IOException {
return parse(name, includeStatus, false, source, now, xContentType, false); return parse(name, includeStatus, false, source, now, xContentType, false, sourceSeqNo, sourcePrimaryTerm);
} }
/** /**
@ -95,17 +96,20 @@ public class WatchParser {
* *
*/ */
public Watch parseWithSecrets(String id, boolean includeStatus, BytesReference source, DateTime now, public Watch parseWithSecrets(String id, boolean includeStatus, BytesReference source, DateTime now,
XContentType xContentType, boolean allowRedactedPasswords) throws IOException { XContentType xContentType, boolean allowRedactedPasswords, long sourceSeqNo, long sourcePrimaryTerm
return parse(id, includeStatus, true, source, now, xContentType, allowRedactedPasswords); ) throws IOException {
return parse(id, includeStatus, true, source, now, xContentType, allowRedactedPasswords, sourceSeqNo, sourcePrimaryTerm);
} }
public Watch parseWithSecrets(String id, boolean includeStatus, BytesReference source, DateTime now, public Watch parseWithSecrets(String id, boolean includeStatus, BytesReference source, DateTime now,
XContentType xContentType) throws IOException { XContentType xContentType, long sourceSeqNo, long sourcePrimaryTerm) throws IOException {
return parse(id, includeStatus, true, source, now, xContentType, false); return parse(id, includeStatus, true, source, now, xContentType, false, sourceSeqNo, sourcePrimaryTerm);
} }
private Watch parse(String id, boolean includeStatus, boolean withSecrets, BytesReference source, DateTime now, private Watch parse(String id, boolean includeStatus, boolean withSecrets, BytesReference source, DateTime now,
XContentType xContentType, boolean allowRedactedPasswords) throws IOException { XContentType xContentType, boolean allowRedactedPasswords, long sourceSeqNo, long sourcePrimaryTerm)
throws IOException {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("parsing watch [{}] ", source.utf8ToString()); logger.trace("parsing watch [{}] ", source.utf8ToString());
} }
@ -115,13 +119,14 @@ public class WatchParser {
LoggingDeprecationHandler.INSTANCE, stream), LoggingDeprecationHandler.INSTANCE, stream),
now, withSecrets ? cryptoService : null, allowRedactedPasswords)) { now, withSecrets ? cryptoService : null, allowRedactedPasswords)) {
parser.nextToken(); parser.nextToken();
return parse(id, includeStatus, parser); return parse(id, includeStatus, parser, sourceSeqNo, sourcePrimaryTerm);
} catch (IOException ioe) { } catch (IOException ioe) {
throw ioException("could not parse watch [{}]", ioe, id); throw ioException("could not parse watch [{}]", ioe, id);
} }
} }
public Watch parse(String id, boolean includeStatus, WatcherXContentParser parser) throws IOException { public Watch parse(String id, boolean includeStatus, WatcherXContentParser parser, long sourceSeqNo, long sourcePrimaryTerm)
throws IOException {
Trigger trigger = null; Trigger trigger = null;
ExecutableInput input = defaultInput; ExecutableInput input = defaultInput;
ExecutableCondition condition = defaultCondition; ExecutableCondition condition = defaultCondition;
@ -130,7 +135,6 @@ public class WatchParser {
TimeValue throttlePeriod = null; TimeValue throttlePeriod = null;
Map<String, Object> metatdata = null; Map<String, Object> metatdata = null;
WatchStatus status = null; WatchStatus status = null;
long version = Versions.MATCH_ANY;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
@ -163,8 +167,6 @@ public class WatchParser {
actions = actionRegistry.parseActions(id, parser); actions = actionRegistry.parseActions(id, parser);
} else if (WatchField.METADATA.match(currentFieldName, parser.getDeprecationHandler())) { } else if (WatchField.METADATA.match(currentFieldName, parser.getDeprecationHandler())) {
metatdata = parser.map(); metatdata = parser.map();
} else if (WatchField.VERSION.match(currentFieldName, parser.getDeprecationHandler())) {
version = parser.longValue();
} else if (WatchField.STATUS.match(currentFieldName, parser.getDeprecationHandler())) { } else if (WatchField.STATUS.match(currentFieldName, parser.getDeprecationHandler())) {
if (includeStatus) { if (includeStatus) {
status = WatchStatus.parse(id, parser); status = WatchStatus.parse(id, parser);
@ -198,6 +200,7 @@ public class WatchParser {
} }
return new Watch(id, trigger, input, condition, transform, throttlePeriod, actions, metatdata, status, version); return new Watch(
id, trigger, input, condition, transform, throttlePeriod, actions, metatdata, status, sourceSeqNo, sourcePrimaryTerm);
} }
} }

View File

@ -63,6 +63,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.is;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -134,13 +135,13 @@ public class WatcherIndexingListenerTests extends ESTestCase {
boolean watchActive = randomBoolean(); boolean watchActive = randomBoolean();
boolean isNewWatch = randomBoolean(); boolean isNewWatch = randomBoolean();
Watch watch = mockWatch("_id", watchActive, isNewWatch); Watch watch = mockWatch("_id", watchActive, isNewWatch);
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject())).thenReturn(watch); when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong())).thenReturn(watch);
Engine.Index returnedOperation = listener.preIndex(shardId, operation); Engine.Index returnedOperation = listener.preIndex(shardId, operation);
assertThat(returnedOperation, is(operation)); assertThat(returnedOperation, is(operation));
DateTime now = new DateTime(clock.millis(), UTC); DateTime now = new DateTime(clock.millis(), UTC);
verify(parser).parseWithSecrets(eq(operation.id()), eq(true), eq(BytesArray.EMPTY), eq(now), anyObject()); verify(parser).parseWithSecrets(eq(operation.id()), eq(true), eq(BytesArray.EMPTY), eq(now), anyObject(), anyLong(), anyLong());
if (isNewWatch) { if (isNewWatch) {
if (watchActive) { if (watchActive) {
@ -162,7 +163,7 @@ public class WatcherIndexingListenerTests extends ESTestCase {
when(shardId.getIndexName()).thenReturn(Watch.INDEX); when(shardId.getIndexName()).thenReturn(Watch.INDEX);
when(operation.type()).thenReturn(Watch.DOC_TYPE); when(operation.type()).thenReturn(Watch.DOC_TYPE);
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject())).thenReturn(watch); when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong())).thenReturn(watch);
for (int idx = 0; idx < totalShardCount; idx++) { for (int idx = 0; idx < totalShardCount; idx++) {
final Map<ShardId, ShardAllocationConfiguration> localShards = new HashMap<>(); final Map<ShardId, ShardAllocationConfiguration> localShards = new HashMap<>();
@ -207,7 +208,7 @@ public class WatcherIndexingListenerTests extends ESTestCase {
when(operation.id()).thenReturn(id); when(operation.id()).thenReturn(id);
when(operation.source()).thenReturn(BytesArray.EMPTY); when(operation.source()).thenReturn(BytesArray.EMPTY);
when(shardId.getIndexName()).thenReturn(Watch.INDEX); when(shardId.getIndexName()).thenReturn(Watch.INDEX);
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject())) when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong()))
.thenThrow(new IOException("self thrown")); .thenThrow(new IOException("self thrown"));
ElasticsearchParseException exc = expectThrows(ElasticsearchParseException.class, ElasticsearchParseException exc = expectThrows(ElasticsearchParseException.class,

View File

@ -70,6 +70,7 @@ import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -194,7 +195,7 @@ public class WatcherServiceTests extends ESTestCase {
Watch watch = mock(Watch.class); Watch watch = mock(Watch.class);
when(watchStatus.state()).thenReturn(state); when(watchStatus.state()).thenReturn(state);
when(watch.status()).thenReturn(watchStatus); when(watch.status()).thenReturn(watchStatus);
when(parser.parse(eq(id), eq(true), any(), eq(XContentType.JSON))).thenReturn(watch); when(parser.parse(eq(id), eq(true), any(), eq(XContentType.JSON), anyLong(), anyLong())).thenReturn(watch);
} }
SearchHits searchHits = new SearchHits(hits, new TotalHits(count, TotalHits.Relation.EQUAL_TO), 1.0f); SearchHits searchHits = new SearchHits(hits, new TotalHits(count, TotalHits.Relation.EQUAL_TO), 1.0f);
SearchResponseSections sections = new SearchResponseSections(searchHits, null, null, false, false, null, 1); SearchResponseSections sections = new SearchResponseSections(searchHits, null, null, false, false, null, 1);

View File

@ -101,6 +101,7 @@ import static org.hamcrest.Matchers.sameInstance;
import static org.joda.time.DateTime.now; import static org.joda.time.DateTime.now;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
@ -166,7 +167,7 @@ public class ExecutionServiceTests extends ESTestCase {
DateTime now = new DateTime(clock.millis()); DateTime now = new DateTime(clock.millis());
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5)); TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5));
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
Condition.Result conditionResult = InternalAlwaysCondition.RESULT_INSTANCE; Condition.Result conditionResult = InternalAlwaysCondition.RESULT_INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class); ExecutableCondition condition = mock(ExecutableCondition.class);
@ -270,7 +271,7 @@ public class ExecutionServiceTests extends ESTestCase {
DateTime now = new DateTime(clock.millis()); DateTime now = new DateTime(clock.millis());
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5)); TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5));
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
input = mock(ExecutableInput.class); input = mock(ExecutableInput.class);
Input.Result inputResult = mock(Input.Result.class); Input.Result inputResult = mock(Input.Result.class);
@ -339,7 +340,7 @@ public class ExecutionServiceTests extends ESTestCase {
DateTime now = new DateTime(clock.millis()); DateTime now = new DateTime(clock.millis());
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5)); TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5));
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
ExecutableCondition condition = mock(ExecutableCondition.class); ExecutableCondition condition = mock(ExecutableCondition.class);
Condition.Result conditionResult = mock(Condition.Result.class); Condition.Result conditionResult = mock(Condition.Result.class);
@ -404,7 +405,7 @@ public class ExecutionServiceTests extends ESTestCase {
DateTime now = new DateTime(clock.millis()); DateTime now = new DateTime(clock.millis());
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5)); TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), now, event, timeValueSeconds(5));
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
Condition.Result conditionResult = InternalAlwaysCondition.RESULT_INSTANCE; Condition.Result conditionResult = InternalAlwaysCondition.RESULT_INSTANCE;
ExecutableCondition condition = mock(ExecutableCondition.class); ExecutableCondition condition = mock(ExecutableCondition.class);
@ -464,7 +465,7 @@ public class ExecutionServiceTests extends ESTestCase {
GetResponse getResponse = mock(GetResponse.class); GetResponse getResponse = mock(GetResponse.class);
when(getResponse.isExists()).thenReturn(true); when(getResponse.isExists()).thenReturn(true);
mockGetWatchResponse(client, "_id", getResponse); mockGetWatchResponse(client, "_id", getResponse);
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
DateTime now = new DateTime(clock.millis()); DateTime now = new DateTime(clock.millis());
ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now); ScheduleTriggerEvent event = new ScheduleTriggerEvent("_id", now, now);
@ -838,7 +839,7 @@ public class ExecutionServiceTests extends ESTestCase {
when(getResponse.isExists()).thenReturn(true); when(getResponse.isExists()).thenReturn(true);
when(getResponse.getId()).thenReturn("foo"); when(getResponse.getId()).thenReturn("foo");
mockGetWatchResponse(client, "foo", getResponse); mockGetWatchResponse(client, "foo", getResponse);
when(parser.parseWithSecrets(eq("foo"), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq("foo"), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
// execute needs to fail as well as storing the history // execute needs to fail as well as storing the history
doThrow(new EsRejectedExecutionException()).when(executor).execute(any()); doThrow(new EsRejectedExecutionException()).when(executor).execute(any());
@ -971,7 +972,7 @@ public class ExecutionServiceTests extends ESTestCase {
GetResponse getResponse = mock(GetResponse.class); GetResponse getResponse = mock(GetResponse.class);
when(getResponse.isExists()).thenReturn(true); when(getResponse.isExists()).thenReturn(true);
mockGetWatchResponse(client, "_id", getResponse); mockGetWatchResponse(client, "_id", getResponse);
when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any())).thenReturn(watch); when(parser.parseWithSecrets(eq(watch.id()), eq(true), any(), any(), any(), anyLong(), anyLong())).thenReturn(watch);
WatchRecord.MessageWatchRecord record = mock(WatchRecord.MessageWatchRecord.class); WatchRecord.MessageWatchRecord record = mock(WatchRecord.MessageWatchRecord.class);
when(record.state()).thenReturn(ExecutionState.EXECUTION_NOT_NEEDED); when(record.state()).thenReturn(ExecutionState.EXECUTION_NOT_NEEDED);
@ -1017,7 +1018,7 @@ public class ExecutionServiceTests extends ESTestCase {
public void testUpdateWatchStatusDoesNotUpdateState() throws Exception { public void testUpdateWatchStatusDoesNotUpdateState() throws Exception {
WatchStatus status = new WatchStatus(DateTime.now(UTC), Collections.emptyMap()); WatchStatus status = new WatchStatus(DateTime.now(UTC), Collections.emptyMap());
Watch watch = new Watch("_id", new ManualTrigger(), new ExecutableNoneInput(), InternalAlwaysCondition.INSTANCE, null, null, Watch watch = new Watch("_id", new ManualTrigger(), new ExecutableNoneInput(), InternalAlwaysCondition.INSTANCE, null, null,
Collections.emptyList(), null, status, 1L); Collections.emptyList(), null, status, 1L, 1L);
final AtomicBoolean assertionsTriggered = new AtomicBoolean(false); final AtomicBoolean assertionsTriggered = new AtomicBoolean(false);
doAnswer(invocation -> { doAnswer(invocation -> {

View File

@ -55,7 +55,6 @@ import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import javax.mail.internet.AddressException; import javax.mail.internet.AddressException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -134,7 +133,7 @@ public final class WatcherTestUtils {
null, null,
new ArrayList<>(), new ArrayList<>(),
null, null,
new WatchStatus(new DateTime(0, UTC), emptyMap()), 1L); new WatchStatus(new DateTime(0, UTC), emptyMap()), 1L, 1L);
TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(), TriggeredExecutionContext context = new TriggeredExecutionContext(watch.id(),
new DateTime(0, UTC), new DateTime(0, UTC),
new ScheduleTriggerEvent(watch.id(), new DateTime(0, UTC), new DateTime(0, UTC)), new ScheduleTriggerEvent(watch.id(), new DateTime(0, UTC), new DateTime(0, UTC)),
@ -181,7 +180,7 @@ public final class WatcherTestUtils {
new TimeValue(0), new TimeValue(0),
actions, actions,
Collections.singletonMap("foo", "bar"), Collections.singletonMap("foo", "bar"),
new WatchStatus(now, statuses), 1L); new WatchStatus(now, statuses), 1L, 1L);
} }
public static SearchType getRandomSupportedSearchType() { public static SearchType getRandomSupportedSearchType() {

View File

@ -57,7 +57,7 @@ public class ScheduleEngineTriggerBenchmark {
List<Watch> watches = new ArrayList<>(numWatches); List<Watch> watches = new ArrayList<>(numWatches);
for (int i = 0; i < numWatches; i++) { for (int i = 0; i < numWatches; i++) {
watches.add(new Watch("job_" + i, new ScheduleTrigger(interval(interval + "s")), new ExecutableNoneInput(), watches.add(new Watch("job_" + i, new ScheduleTrigger(interval(interval + "s")), new ExecutableNoneInput(),
InternalAlwaysCondition.INSTANCE, null, null, Collections.emptyList(), null, null, 1L)); InternalAlwaysCondition.INSTANCE, null, null, Collections.emptyList(), null, null, 1L, 1L));
} }
ScheduleRegistry scheduleRegistry = new ScheduleRegistry(emptySet()); ScheduleRegistry scheduleRegistry = new ScheduleRegistry(emptySet());

View File

@ -110,7 +110,8 @@ public class WatchAckTests extends AbstractWatcherIntegrationTestCase {
GetWatchResponse getWatchResponse = watcherClient().prepareGetWatch("_id").get(); GetWatchResponse getWatchResponse = watcherClient().prepareGetWatch("_id").get();
assertThat(getWatchResponse.isFound(), is(true)); assertThat(getWatchResponse.isFound(), is(true));
Watch parsedWatch = watchParser().parse(getWatchResponse.getId(), true, getWatchResponse.getSource().getBytes(), XContentType.JSON); Watch parsedWatch = watchParser().parse(getWatchResponse.getId(), true, getWatchResponse.getSource().getBytes(),
XContentType.JSON, getWatchResponse.getSeqNo(), getWatchResponse.getPrimaryTerm());
assertThat(parsedWatch.status().actionStatus("_a1").ackStatus().state(), assertThat(parsedWatch.status().actionStatus("_a1").ackStatus().state(),
is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(), assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(),
@ -178,7 +179,8 @@ public class WatchAckTests extends AbstractWatcherIntegrationTestCase {
GetWatchResponse getWatchResponse = watcherClient().prepareGetWatch("_id").get(); GetWatchResponse getWatchResponse = watcherClient().prepareGetWatch("_id").get();
assertThat(getWatchResponse.isFound(), is(true)); assertThat(getWatchResponse.isFound(), is(true));
Watch parsedWatch = watchParser().parse(getWatchResponse.getId(), true, getWatchResponse.getSource().getBytes(), XContentType.JSON); Watch parsedWatch = watchParser().parse(getWatchResponse.getId(), true,
getWatchResponse.getSource().getBytes(), XContentType.JSON, getWatchResponse.getSeqNo(), getWatchResponse.getPrimaryTerm());
assertThat(parsedWatch.status().actionStatus("_a1").ackStatus().state(), assertThat(parsedWatch.status().actionStatus("_a1").ackStatus().state(),
is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(), assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(),
@ -220,7 +222,8 @@ public class WatchAckTests extends AbstractWatcherIntegrationTestCase {
refresh(); refresh();
GetResponse getResponse = client().get(new GetRequest(Watch.INDEX, Watch.DOC_TYPE, "_name")).actionGet(); GetResponse getResponse = client().get(new GetRequest(Watch.INDEX, Watch.DOC_TYPE, "_name")).actionGet();
Watch indexedWatch = watchParser().parse("_name", true, getResponse.getSourceAsBytesRef(), XContentType.JSON); Watch indexedWatch = watchParser().parse("_name", true, getResponse.getSourceAsBytesRef(), XContentType.JSON,
getResponse.getSeqNo(), getResponse.getPrimaryTerm());
assertThat(watchResponse.getStatus().actionStatus("_id").ackStatus().state(), assertThat(watchResponse.getStatus().actionStatus("_id").ackStatus().state(),
equalTo(indexedWatch.status().actionStatus("_id").ackStatus().state())); equalTo(indexedWatch.status().actionStatus("_id").ackStatus().state()));

View File

@ -39,6 +39,7 @@ import java.util.Collections;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO; import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
import static org.elasticsearch.test.ClusterServiceUtils.createClusterService;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
@ -61,7 +62,7 @@ public class TransportAckWatchActionTests extends ESTestCase {
client = mock(Client.class); client = mock(Client.class);
when(client.threadPool()).thenReturn(threadPool); when(client.threadPool()).thenReturn(threadPool);
action = new TransportAckWatchAction(transportService, new ActionFilters(Collections.emptySet()), action = new TransportAckWatchAction(transportService, new ActionFilters(Collections.emptySet()),
Clock.systemUTC(), new XPackLicenseState(Settings.EMPTY), watchParser, client); Clock.systemUTC(), new XPackLicenseState(Settings.EMPTY), watchParser, client, createClusterService(threadPool));
} }
public void testWatchNotFound() { public void testWatchNotFound() {

View File

@ -36,6 +36,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -57,7 +58,8 @@ public class TransportPutWatchActionTests extends ESTestCase {
TransportService transportService = mock(TransportService.class); TransportService transportService = mock(TransportService.class);
WatchParser parser = mock(WatchParser.class); WatchParser parser = mock(WatchParser.class);
when(parser.parseWithSecrets(eq("_id"), eq(false), anyObject(), anyObject(), anyObject(), anyBoolean())).thenReturn(watch); when(parser.parseWithSecrets(eq("_id"), eq(false), anyObject(), anyObject(), anyObject(), anyBoolean(), anyLong(), anyLong()))
.thenReturn(watch);
Client client = mock(Client.class); Client client = mock(Client.class);
when(client.threadPool()).thenReturn(threadPool); when(client.threadPool()).thenReturn(threadPool);

View File

@ -5,8 +5,8 @@
*/ */
package org.elasticsearch.xpack.watcher.trigger.schedule.engine; package org.elasticsearch.xpack.watcher.trigger.schedule.engine;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.watcher.trigger.TriggerEvent; import org.elasticsearch.xpack.core.watcher.trigger.TriggerEvent;
import org.elasticsearch.xpack.core.watcher.watch.ClockMock; import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
@ -273,6 +273,6 @@ public class TickerScheduleEngineTests extends ESTestCase {
private Watch createWatch(String name, Schedule schedule) { private Watch createWatch(String name, Schedule schedule) {
return new Watch(name, new ScheduleTrigger(schedule), new ExecutableNoneInput(), return new Watch(name, new ScheduleTrigger(schedule), new ExecutableNoneInput(),
InternalAlwaysCondition.INSTANCE, null, null, InternalAlwaysCondition.INSTANCE, null, null,
Collections.emptyList(), null, null, Versions.MATCH_ANY); Collections.emptyList(), null, null, SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM);
} }
} }

View File

@ -208,13 +208,16 @@ public class WatchTests extends ESTestCase {
TimeValue throttlePeriod = randomBoolean() ? null : TimeValue.timeValueSeconds(randomIntBetween(5, 10000)); TimeValue throttlePeriod = randomBoolean() ? null : TimeValue.timeValueSeconds(randomIntBetween(5, 10000));
Watch watch = new Watch("_name", trigger, input, condition, transform, throttlePeriod, actions, metadata, watchStatus, 1L); final long sourceSeqNo = randomNonNegativeLong();
final long sourcePrimaryTerm = randomLongBetween(1, 200);
Watch watch = new Watch("_name", trigger, input, condition, transform, throttlePeriod, actions, metadata, watchStatus,
sourceSeqNo, sourcePrimaryTerm);
BytesReference bytes = BytesReference.bytes(jsonBuilder().value(watch)); BytesReference bytes = BytesReference.bytes(jsonBuilder().value(watch));
logger.info("{}", bytes.utf8ToString()); logger.info("{}", bytes.utf8ToString());
WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock); WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock);
Watch parsedWatch = watchParser.parse("_name", includeStatus, bytes, XContentType.JSON); Watch parsedWatch = watchParser.parse("_name", includeStatus, bytes, XContentType.JSON, sourceSeqNo, sourcePrimaryTerm);
if (includeStatus) { if (includeStatus) {
assertThat(parsedWatch.status(), equalTo(watchStatus)); assertThat(parsedWatch.status(), equalTo(watchStatus));
@ -227,6 +230,8 @@ public class WatchTests extends ESTestCase {
} }
assertThat(parsedWatch.metadata(), equalTo(metadata)); assertThat(parsedWatch.metadata(), equalTo(metadata));
assertThat(parsedWatch.actions(), equalTo(actions)); assertThat(parsedWatch.actions(), equalTo(actions));
assertThat(parsedWatch.getSourceSeqNo(), equalTo(sourceSeqNo));
assertThat(parsedWatch.getSourcePrimaryTerm(), equalTo(sourcePrimaryTerm));
} }
public void testThatBothStatusFieldsCanBeRead() throws Exception { public void testThatBothStatusFieldsCanBeRead() throws Exception {
@ -256,7 +261,7 @@ public class WatchTests extends ESTestCase {
WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock); WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock);
XContentBuilder builder = jsonBuilder().startObject().startObject("trigger").endObject().field("status", watchStatus).endObject(); XContentBuilder builder = jsonBuilder().startObject().startObject("trigger").endObject().field("status", watchStatus).endObject();
Watch watch = watchParser.parse("foo", true, BytesReference.bytes(builder), XContentType.JSON); Watch watch = watchParser.parse("foo", true, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L);
assertThat(watch.status().state().getTimestamp().getMillis(), is(clock.millis())); assertThat(watch.status().state().getTimestamp().getMillis(), is(clock.millis()));
for (ActionWrapper action : actions) { for (ActionWrapper action : actions) {
assertThat(watch.status().actionStatus(action.id()), is(actionsStatuses.get(action.id()))); assertThat(watch.status().actionStatus(action.id()), is(actionsStatuses.get(action.id())));
@ -284,7 +289,7 @@ public class WatchTests extends ESTestCase {
.endObject(); .endObject();
WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock); WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, clock);
try { try {
watchParser.parse("failure", false, BytesReference.bytes(jsonBuilder), XContentType.JSON); watchParser.parse("failure", false, BytesReference.bytes(jsonBuilder), XContentType.JSON, 1L, 1L);
fail("This watch should fail to parse as actions is an array"); fail("This watch should fail to parse as actions is an array");
} catch (ElasticsearchParseException pe) { } catch (ElasticsearchParseException pe) {
assertThat(pe.getMessage().contains("could not parse actions for watch [failure]"), is(true)); assertThat(pe.getMessage().contains("could not parse actions for watch [failure]"), is(true));
@ -309,7 +314,7 @@ public class WatchTests extends ESTestCase {
.endObject(); .endObject();
builder.endObject(); builder.endObject();
WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, Clock.systemUTC()); WatchParser watchParser = new WatchParser(triggerService, actionRegistry, inputRegistry, null, Clock.systemUTC());
Watch watch = watchParser.parse("failure", false, BytesReference.bytes(builder), XContentType.JSON); Watch watch = watchParser.parse("failure", false, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L);
assertThat(watch, notNullValue()); assertThat(watch, notNullValue());
assertThat(watch.trigger(), instanceOf(ScheduleTrigger.class)); assertThat(watch.trigger(), instanceOf(ScheduleTrigger.class));
assertThat(watch.input(), instanceOf(ExecutableNoneInput.class)); assertThat(watch.input(), instanceOf(ExecutableNoneInput.class));
@ -375,7 +380,7 @@ public class WatchTests extends ESTestCase {
builder.endObject(); builder.endObject();
// parse in default mode: // parse in default mode:
Watch watch = watchParser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON); Watch watch = watchParser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L);
assertThat(((ScriptCondition) watch.condition()).getScript().getLang(), equalTo(Script.DEFAULT_SCRIPT_LANG)); assertThat(((ScriptCondition) watch.condition()).getScript().getLang(), equalTo(Script.DEFAULT_SCRIPT_LANG));
WatcherSearchTemplateRequest request = ((SearchInput) watch.input().input()).getRequest(); WatcherSearchTemplateRequest request = ((SearchInput) watch.input().input()).getRequest();
SearchRequest searchRequest = searchTemplateService.toSearchRequest(request); SearchRequest searchRequest = searchTemplateService.toSearchRequest(request);
@ -394,7 +399,7 @@ public class WatchTests extends ESTestCase {
builder.endObject(); builder.endObject();
WatchParser parser = createWatchparser(); WatchParser parser = createWatchparser();
Watch watch = parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON); Watch watch = parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L);
assertThat(watch, is(notNullValue())); assertThat(watch, is(notNullValue()));
assertThat(watch.input().type(), is(NoneInput.TYPE)); assertThat(watch.input().type(), is(NoneInput.TYPE));
} }
@ -410,7 +415,7 @@ public class WatchTests extends ESTestCase {
builder.endObject(); builder.endObject();
WatchParser parser = createWatchparser(); WatchParser parser = createWatchparser();
Watch watch = parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON); Watch watch = parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L);
assertThat(watch, is(notNullValue())); assertThat(watch, is(notNullValue()));
assertThat(watch.actions(), hasSize(0)); assertThat(watch.actions(), hasSize(0));
} }
@ -429,7 +434,7 @@ public class WatchTests extends ESTestCase {
WatchParser parser = createWatchparser(); WatchParser parser = createWatchparser();
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
() -> parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON)); () -> parser.parse("_id", false, BytesReference.bytes(builder), XContentType.JSON, 1L, 1L));
assertThat(e.getMessage(), is("could not parse watch [_id]. missing required field [trigger]")); assertThat(e.getMessage(), is("could not parse watch [_id]. missing required field [trigger]"));
} }
} }