Watcher: Replace _status field with status (elastic/x-pack-elasticsearch#1285)

As fields with underscores will be disallowed in master, and we have to
prepare the upgrade, this commit renames the _status field to status.

When the 5.x upgrade logic is in place in the 5.x we can remove all the
old style _status handling from the master branch.

Note: All the BWC compatibility tests, that load 5.x indices are now
faking a finished upgrade by adding the `status` field to the mapping
of the watches index.

Original commit: elastic/x-pack-elasticsearch@9d5cc9aaec
This commit is contained in:
Alexander Reelsen 2017-05-04 10:08:34 +02:00 committed by GitHub
parent 8633fd1f07
commit 4078b2f1b2
26 changed files with 191 additions and 104 deletions

View File

@ -3,7 +3,7 @@
<<actions-ack-throttle, Acknowledging>> a watch enables you to manually throttle <<actions-ack-throttle, Acknowledging>> a watch enables you to manually throttle
execution of the watch's actions. An action's _acknowledgement state_ is stored execution of the watch's actions. An action's _acknowledgement state_ is stored
in the `_status.actions.<id>.ack.state` structure. in the `status.actions.<id>.ack.state` structure.
To demonstrate let's create a new watch: To demonstrate let's create a new watch:
@ -58,7 +58,7 @@ The action state of a newly-created watch is `awaits_successful_execution`:
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { "status": {
"version": 1, "version": 1,
"actions": { "actions": {
"test_index": { "test_index": {
@ -73,9 +73,9 @@ The action state of a newly-created watch is `awaits_successful_execution`:
"watch": ... "watch": ...
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"state": \.\.\./"state": "$body._status.state"/] // TESTRESPONSE[s/"state": \.\.\./"state": "$body.status.state"/]
// TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/] // TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/]
// TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body._status.actions.test_index.ack.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body.status.actions.test_index.ack.timestamp"/]
When the watch executes and the condition matches, the value of the `ack.state` When the watch executes and the condition matches, the value of the `ack.state`
changes to `ackable`. Let's force execution of the watch and fetch it again to changes to `ackable`. Let's force execution of the watch and fetch it again to
@ -95,7 +95,7 @@ and the action is now in `ackable` state:
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { "status": {
"version": 1, "version": 1,
"actions": { "actions": {
"test_index": { "test_index": {
@ -110,9 +110,9 @@ and the action is now in `ackable` state:
"watch": ... "watch": ...
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"state": \.\.\./"state": "$body._status.state"/] // TESTRESPONSE[s/"state": \.\.\./"state": "$body.status.state"/]
// TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/] // TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/]
// TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body._status.actions.test_index.ack.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body.status.actions.test_index.ack.timestamp"/]
Now we can acknowledge it: Now we can acknowledge it:
@ -128,7 +128,7 @@ GET _xpack/watcher/watch/my_watch
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { "status": {
"version": 1, "version": 1,
"actions": { "actions": {
"test_index": { "test_index": {
@ -143,9 +143,9 @@ GET _xpack/watcher/watch/my_watch
"watch": ... "watch": ...
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"state": \.\.\./"state": "$body._status.state"/] // TESTRESPONSE[s/"state": \.\.\./"state": "$body.status.state"/]
// TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/] // TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/]
// TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body._status.actions.test_index.ack.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-05-26T18:04:27.723Z"/"timestamp": "$body.status.actions.test_index.ack.timestamp"/]
Acknowledging an action throttles further executions of that action until its Acknowledging an action throttles further executions of that action until its
`ack.state` is reset to `awaits_successful_execution`. This happens when the `ack.state` is reset to `awaits_successful_execution`. This happens when the
@ -195,7 +195,7 @@ The response format looks like:
[source,js] [source,js]
-------------------------------------------------- --------------------------------------------------
{ {
"_status": { "status": {
"last_checked": "2015-05-26T18:21:08.630Z", "last_checked": "2015-05-26T18:21:08.630Z",
"last_met_condition": "2015-05-26T18:21:08.630Z", "last_met_condition": "2015-05-26T18:21:08.630Z",
"actions": { "actions": {

View File

@ -19,7 +19,7 @@ GET _xpack/watcher/watch/my_watch
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { "status": {
"state" : { "state" : {
"active" : false, "active" : false,
"timestamp" : "2015-08-20T12:21:32.734Z" "timestamp" : "2015-08-20T12:21:32.734Z"
@ -30,10 +30,10 @@ GET _xpack/watcher/watch/my_watch
"watch": ... "watch": ...
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/2015-08-20T12:21:32.734Z/$body._status.state.timestamp/] // TESTRESPONSE[s/2015-08-20T12:21:32.734Z/$body.status.state.timestamp/]
// TESTRESPONSE[s/"actions": \.\.\./"actions": "$body._status.actions"/] // TESTRESPONSE[s/"actions": \.\.\./"actions": "$body.status.actions"/]
// TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/] // TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/]
// TESTRESPONSE[s/"version": 1/"version": $body._status.version/] // TESTRESPONSE[s/"version": 1/"version": $body.status.version/]
You can activate the watch by executing the following API call: You can activate the watch by executing the following API call:
@ -49,7 +49,7 @@ The new state of the watch is returned as part of its overall status:
[source,js] [source,js]
-------------------------------------------------- --------------------------------------------------
{ {
"_status": { "status": {
"state" : { "state" : {
"active" : true, "active" : true,
"timestamp" : "2015-09-04T08:39:46.816Z" "timestamp" : "2015-09-04T08:39:46.816Z"
@ -59,6 +59,6 @@ The new state of the watch is returned as part of its overall status:
} }
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/2015-09-04T08:39:46.816Z/$body._status.state.timestamp/] // TESTRESPONSE[s/2015-09-04T08:39:46.816Z/$body.status.state.timestamp/]
// TESTRESPONSE[s/"actions": \.\.\./"actions": "$body._status.actions"/] // TESTRESPONSE[s/"actions": \.\.\./"actions": "$body.status.actions"/]
// TESTRESPONSE[s/"version": 1/"version": $body._status.version/] // TESTRESPONSE[s/"version": 1/"version": $body.status.version/]

View File

@ -19,7 +19,7 @@ GET _xpack/watcher/watch/my_watch
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { "status": {
"state" : { "state" : {
"active" : true, "active" : true,
"timestamp" : "2015-08-20T12:21:32.734Z" "timestamp" : "2015-08-20T12:21:32.734Z"
@ -30,10 +30,10 @@ GET _xpack/watcher/watch/my_watch
"watch": ... "watch": ...
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/2015-08-20T12:21:32.734Z/$body._status.state.timestamp/] // TESTRESPONSE[s/2015-08-20T12:21:32.734Z/$body.status.state.timestamp/]
// TESTRESPONSE[s/"actions": \.\.\./"actions": "$body._status.actions"/] // TESTRESPONSE[s/"actions": \.\.\./"actions": "$body.status.actions"/]
// TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/] // TESTRESPONSE[s/"watch": \.\.\./"watch": "$body.watch"/]
// TESTRESPONSE[s/"version": 1/"version": $body._status.version/] // TESTRESPONSE[s/"version": 1/"version": $body.status.version/]
You can deactivate the watch by executing the following API call: You can deactivate the watch by executing the following API call:
@ -49,7 +49,7 @@ The new state of the watch is returned as part of its overall status:
[source,js] [source,js]
-------------------------------------------------- --------------------------------------------------
{ {
"_status": { "status": {
"state" : { "state" : {
"active" : false, "active" : false,
"timestamp" : "2015-09-04T08:39:46.816Z" "timestamp" : "2015-09-04T08:39:46.816Z"
@ -59,6 +59,6 @@ The new state of the watch is returned as part of its overall status:
} }
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/2015-09-04T08:39:46.816Z/$body._status.state.timestamp/] // TESTRESPONSE[s/2015-09-04T08:39:46.816Z/$body.status.state.timestamp/]
// TESTRESPONSE[s/"actions": \.\.\./"actions": "$body._status.actions"/] // TESTRESPONSE[s/"actions": \.\.\./"actions": "$body.status.actions"/]
// TESTRESPONSE[s/"version": 1/"version": $body._status.version/] // TESTRESPONSE[s/"version": 1/"version": $body.status.version/]

View File

@ -104,7 +104,7 @@ This is an example of the output:
} }
}, },
"state": "executed", "state": "executed",
"_status": { "status": {
"version": 1, "version": 1,
"state": { "state": {
"active": true, "active": true,
@ -179,10 +179,10 @@ This is an example of the output:
// TESTRESPONSE[s/"triggered_time": "2015-06-02T23:17:55.124Z"/"triggered_time": "$body.watch_record.trigger_event.triggered_time"/] // TESTRESPONSE[s/"triggered_time": "2015-06-02T23:17:55.124Z"/"triggered_time": "$body.watch_record.trigger_event.triggered_time"/]
// TESTRESPONSE[s/"scheduled_time": "2015-06-02T23:17:55.124Z"/"scheduled_time": "$body.watch_record.trigger_event.manual.schedule.scheduled_time"/] // TESTRESPONSE[s/"scheduled_time": "2015-06-02T23:17:55.124Z"/"scheduled_time": "$body.watch_record.trigger_event.manual.schedule.scheduled_time"/]
// TESTRESPONSE[s/"execution_time": "2015-06-02T23:17:55.124Z"/"execution_time": "$body.watch_record.result.execution_time"/] // TESTRESPONSE[s/"execution_time": "2015-06-02T23:17:55.124Z"/"execution_time": "$body.watch_record.result.execution_time"/]
// TESTRESPONSE[s/"timestamp": "2015-06-02T23:17:55.111Z"/"timestamp": "$body.watch_record._status.state.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-06-02T23:17:55.111Z"/"timestamp": "$body.watch_record.status.state.timestamp"/]
// TESTRESPONSE[s/"timestamp": "2015-06-02T23:17:55.124Z"/"timestamp": "$body.watch_record._status.actions.test_index.ack.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-06-02T23:17:55.124Z"/"timestamp": "$body.watch_record.status.actions.test_index.ack.timestamp"/]
// TESTRESPONSE[s/"last_checked": "2015-06-02T23:17:55.124Z"/"last_checked": "$body.watch_record._status.last_checked"/] // TESTRESPONSE[s/"last_checked": "2015-06-02T23:17:55.124Z"/"last_checked": "$body.watch_record.status.last_checked"/]
// TESTRESPONSE[s/"last_met_condition": "2015-06-02T23:17:55.124Z"/"last_met_condition": "$body.watch_record._status.last_met_condition"/] // TESTRESPONSE[s/"last_met_condition": "2015-06-02T23:17:55.124Z"/"last_met_condition": "$body.watch_record.status.last_met_condition"/]
// TESTRESPONSE[s/"execution_duration": 12608/"execution_duration": "$body.watch_record.result.execution_duration"/] // TESTRESPONSE[s/"execution_duration": 12608/"execution_duration": "$body.watch_record.result.execution_duration"/]
// TESTRESPONSE[s/"id": "AVSHKzPa9zx62AzUzFXY"/"id": "$body.watch_record.result.actions.0.index.response.id"/] // TESTRESPONSE[s/"id": "AVSHKzPa9zx62AzUzFXY"/"id": "$body.watch_record.result.actions.0.index.response.id"/]
// TESTRESPONSE[s/"node": "my_node"/"node": "$body.watch_record.node"/] // TESTRESPONSE[s/"node": "my_node"/"node": "$body.watch_record.node"/]

View File

@ -19,7 +19,7 @@ Response:
{ {
"found": true, "found": true,
"_id": "my_watch", "_id": "my_watch",
"_status": { <1> "status": { <1>
"version": 1, "version": 1,
"state": { "state": {
"active": true, "active": true,
@ -63,5 +63,5 @@ Response:
} }
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"timestamp": "2015-05-26T18:21:08.630Z"/"timestamp": "$body._status.state.timestamp"/] // TESTRESPONSE[s/"timestamp": "2015-05-26T18:21:08.630Z"/"timestamp": "$body.status.state.timestamp"/]
<1> The current status of the watch <1> The current status of the watch

View File

@ -4,7 +4,7 @@
<<actions-ack-throttle, Acknowledging>> a watch enables you to manually throttle <<actions-ack-throttle, Acknowledging>> a watch enables you to manually throttle
execution of the watch actions. The action's _acknowledgement state_ is stored in execution of the watch actions. The action's _acknowledgement state_ is stored in
the `_status.actions.<id>.ack.state` structure. the `status.actions.<id>.ack.state` structure.
The current status of the watch and the state of its actions are returned as part The current status of the watch and the state of its actions are returned as part
of the <<api-java-get-watch, Get Watch API>> response: of the <<api-java-get-watch, Get Watch API>> response:

View File

@ -137,7 +137,7 @@ public abstract class WatchRecord implements ToXContentObject {
builder.field(Field.STATE.getPreferredName(), state.id()); builder.field(Field.STATE.getPreferredName(), state.id());
if (watch != null && watch.status() != null) { if (watch != null && watch.status() != null) {
builder.field("_status", watch.status(), params); builder.field(Field.STATUS.getPreferredName(), watch.status(), params);
} }
builder.field(Field.TRIGGER_EVENT.getPreferredName()); builder.field(Field.TRIGGER_EVENT.getPreferredName());
@ -195,7 +195,7 @@ public abstract class WatchRecord implements ToXContentObject {
ParseField TRIGGER_EVENT = new ParseField("trigger_event"); ParseField TRIGGER_EVENT = new ParseField("trigger_event");
ParseField MESSAGES = new ParseField("messages"); ParseField MESSAGES = new ParseField("messages");
ParseField STATE = new ParseField("state"); ParseField STATE = new ParseField("state");
ParseField STATUS = new ParseField("_status"); ParseField STATUS = new ParseField("status");
ParseField VARS = new ParseField("vars"); ParseField VARS = new ParseField("vars");
ParseField METADATA = new ParseField("metadata"); ParseField METADATA = new ParseField("metadata");
ParseField EXECUTION_RESULT = new ParseField("result"); ParseField EXECUTION_RESULT = new ParseField("result");

View File

@ -45,7 +45,7 @@ public class RestGetWatchAction extends WatcherRestHandler {
.field("_id", response.getId()); .field("_id", response.getId());
if (response.isFound()) { if (response.isFound()) {
ToXContent.MapParams xContentParams = new ToXContent.MapParams(request.params()); ToXContent.MapParams xContentParams = new ToXContent.MapParams(request.params());
builder.field("_status", response.getStatus(), xContentParams); builder.field("status", response.getStatus(), xContentParams);
builder.field("watch", response.getSource(), xContentParams); builder.field("watch", response.getSource(), xContentParams);
} }
builder.endObject(); builder.endObject();

View File

@ -37,7 +37,7 @@ import static java.util.Collections.unmodifiableMap;
public class WatcherIndexTemplateRegistry extends AbstractComponent implements ClusterStateListener { public class WatcherIndexTemplateRegistry extends AbstractComponent implements ClusterStateListener {
private static final String FORBIDDEN_INDEX_SETTING = "index.mapper.dynamic"; private static final String FORBIDDEN_INDEX_SETTING = "index.mapper.dynamic";
public static final String INDEX_TEMPLATE_VERSION = "4"; public static final String INDEX_TEMPLATE_VERSION = "5";
public static final String HISTORY_TEMPLATE_NAME = "watch_history_" + INDEX_TEMPLATE_VERSION; public static final String HISTORY_TEMPLATE_NAME = "watch_history_" + INDEX_TEMPLATE_VERSION;
public static final String TRIGGERED_TEMPLATE_NAME = "triggered_watches"; public static final String TRIGGERED_TEMPLATE_NAME = "triggered_watches";

View File

@ -303,7 +303,7 @@ public class Watch implements ToXContentObject {
actions = actionRegistry.parseActions(id, parser); actions = actionRegistry.parseActions(id, parser);
} else if (Field.METADATA.match(currentFieldName)) { } else if (Field.METADATA.match(currentFieldName)) {
metatdata = parser.map(); metatdata = parser.map();
} else if (Field.STATUS.match(currentFieldName)) { } else if (Field.STATUS.match(currentFieldName) || Field.STATUS_V5.match(currentFieldName)) {
if (includeStatus) { if (includeStatus) {
status = WatchStatus.parse(id, parser, clock); status = WatchStatus.parse(id, parser, clock);
} else { } else {
@ -349,7 +349,8 @@ public class Watch implements ToXContentObject {
ParseField THROTTLE_PERIOD = new ParseField("throttle_period_in_millis"); ParseField THROTTLE_PERIOD = new ParseField("throttle_period_in_millis");
ParseField THROTTLE_PERIOD_HUMAN = new ParseField("throttle_period"); ParseField THROTTLE_PERIOD_HUMAN = new ParseField("throttle_period");
ParseField METADATA = new ParseField("metadata"); ParseField METADATA = new ParseField("metadata");
ParseField STATUS = new ParseField("_status"); ParseField STATUS = new ParseField("status");
ParseField STATUS_V5 = new ParseField("_status");
} }
private static final Pattern NO_WS_PATTERN = Pattern.compile("\\S+"); private static final Pattern NO_WS_PATTERN = Pattern.compile("\\S+");

View File

@ -103,6 +103,11 @@
"enabled" : false, "enabled" : false,
"dynamic" : true "dynamic" : true
}, },
"status": {
"type": "object",
"enabled" : false,
"dynamic" : true
},
"messages": { "messages": {
"type": "text" "type": "text"
}, },

View File

@ -15,6 +15,11 @@
"enabled" : false, "enabled" : false,
"dynamic" : true "dynamic" : true
}, },
"status": {
"type": "object",
"enabled" : false,
"dynamic" : true
},
"trigger" : { "trigger" : {
"type": "object", "type": "object",
"enabled" : false, "enabled" : false,

View File

@ -21,12 +21,15 @@ import org.elasticsearch.xpack.watcher.transport.actions.stats.WatcherStatsRespo
import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule; import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule;
import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule.Interval; import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule.Interval;
import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger;
import org.elasticsearch.xpack.watcher.watch.Watch;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds; import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasEntry;
@ -57,6 +60,17 @@ public class OldWatcherIndicesBackwardsCompatibilityTests extends AbstractOldXPa
.stream().map(WatcherStatsResponse.Node::getWatcherState).collect(Collectors.toList()); .stream().map(WatcherStatsResponse.Node::getWatcherState).collect(Collectors.toList());
assertThat(states, everyItem(is(WatcherState.STARTED))); assertThat(states, everyItem(is(WatcherState.STARTED)));
}); });
// in order to go from 5.x to master, we assume someone executed the upgrade API, which will install the new index templates
// in this test we just do this manually, by adding the _status field to the mapping
// this can be removed once the API supports regular upgrade from 5.x to 6.x
assertAcked(client().admin().indices().preparePutMapping(Watch.INDEX).setType(Watch.DOC_TYPE)
.setSource(jsonBuilder().startObject().startObject("properties").startObject("status")
.field("type", "object")
.field("enabled", false)
.field("dynamic", true).endObject().endObject().endObject())
.get());
try { try {
assertWatchIndexContentsWork(version); assertWatchIndexContentsWork(version);
assertBasicWatchInteractions(); assertBasicWatchInteractions();

View File

@ -147,7 +147,7 @@ public class ManualExecutionTests extends AbstractWatcherIntegrationTestCase {
is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION));
} }
} else { } else {
String ackState = executeWatchResponse.getRecordSource().getValue("_status.actions.log.ack.state"); String ackState = executeWatchResponse.getRecordSource().getValue("status.actions.log.ack.state");
if (ignoreCondition || conditionAlwaysTrue) { if (ignoreCondition || conditionAlwaysTrue) {
assertThat(ackState, is(ActionStatus.AckStatus.State.ACKABLE.toString().toLowerCase(Locale.ROOT))); assertThat(ackState, is(ActionStatus.AckStatus.State.ACKABLE.toString().toLowerCase(Locale.ROOT)));
} else { } else {

View File

@ -176,32 +176,32 @@ public class HistoryIntegrationTests extends AbstractWatcherIntegrationTestCase
XContentSource source = new XContentSource(hit.getSourceRef(), XContentType.JSON); XContentSource source = new XContentSource(hit.getSourceRef(), XContentType.JSON);
Boolean active = source.getValue("_status.state.active"); Boolean active = source.getValue("status.state.active");
assertThat(active, is(status.state().isActive())); assertThat(active, is(status.state().isActive()));
String timestamp = source.getValue("_status.state.timestamp"); String timestamp = source.getValue("status.state.timestamp");
assertThat(timestamp, is(status.state().getTimestamp().toString())); assertThat(timestamp, is(status.state().getTimestamp().toString()));
String lastChecked = source.getValue("_status.last_checked"); String lastChecked = source.getValue("status.last_checked");
assertThat(lastChecked, is(status.lastChecked().toString())); assertThat(lastChecked, is(status.lastChecked().toString()));
Integer version = source.getValue("_status.version"); Integer version = source.getValue("status.version");
int expectedVersion = (int) (status.version() - 1); int expectedVersion = (int) (status.version() - 1);
assertThat(version, is(expectedVersion)); assertThat(version, is(expectedVersion));
ActionStatus actionStatus = status.actionStatus("_logger"); ActionStatus actionStatus = status.actionStatus("_logger");
String ackStatusState = source.getValue("_status.actions._logger.ack.state").toString().toUpperCase(Locale.ROOT); String ackStatusState = source.getValue("status.actions._logger.ack.state").toString().toUpperCase(Locale.ROOT);
assertThat(ackStatusState, is(actionStatus.ackStatus().state().toString())); assertThat(ackStatusState, is(actionStatus.ackStatus().state().toString()));
Boolean lastExecutionSuccesful = source.getValue("_status.actions._logger.last_execution.successful"); Boolean lastExecutionSuccesful = source.getValue("status.actions._logger.last_execution.successful");
assertThat(lastExecutionSuccesful, is(actionStatus.lastExecution().successful())); assertThat(lastExecutionSuccesful, is(actionStatus.lastExecution().successful()));
// also ensure that the _status field is disabled in the watch history // also ensure that the status field is disabled in the watch history
GetMappingsResponse response = client().admin().indices().prepareGetMappings(".watcher-history*").addTypes("watch_record").get(); GetMappingsResponse response = client().admin().indices().prepareGetMappings(".watcher-history*").addTypes("watch_record").get();
byte[] bytes = response.getMappings().values().iterator().next().value.get("watch_record").source().uncompressed(); byte[] bytes = response.getMappings().values().iterator().next().value.get("watch_record").source().uncompressed();
XContentSource mappingSource = new XContentSource(new BytesArray(bytes), XContentType.JSON); XContentSource mappingSource = new XContentSource(new BytesArray(bytes), XContentType.JSON);
assertThat(mappingSource.getValue("watch_record.properties._status.enabled"), is(false)); assertThat(mappingSource.getValue("watch_record.properties.status.enabled"), is(false));
assertThat(mappingSource.getValue("watch_record.properties._status.properties.status"), is(nullValue())); assertThat(mappingSource.getValue("watch_record.properties.status.properties.status"), is(nullValue()));
assertThat(mappingSource.getValue("watch_record.properties._status.properties.status.properties.active"), is(nullValue())); assertThat(mappingSource.getValue("watch_record.properties.status.properties.status.properties.active"), is(nullValue()));
} }
} }

View File

@ -147,10 +147,10 @@ public class ActivateWatchTests extends AbstractWatcherIntegrationTestCase {
"transform.**", "transform.**",
"actions.**", "actions.**",
"metadata.**", "metadata.**",
"_status.version", "status.version",
"_status.last_checked", "status.last_checked",
"_status.last_met_condition", "status.last_met_condition",
"_status.actions.**"); "status.actions.**");
XContentBuilder builder = new XContentBuilder(XContentType.JSON.xContent(), new BytesStreamOutput(), filters); XContentBuilder builder = new XContentBuilder(XContentType.JSON.xContent(), new BytesStreamOutput(), filters);
source.toXContent(builder, ToXContent.EMPTY_PARAMS); source.toXContent(builder, ToXContent.EMPTY_PARAMS);

View File

@ -81,7 +81,7 @@ public class ExecuteWatchTests extends AbstractWatcherIntegrationTestCase {
assertValue(record, "result.actions.0.type", is("logging")); assertValue(record, "result.actions.0.type", is("logging"));
assertValue(record, "result.actions.0.status", is("success")); assertValue(record, "result.actions.0.status", is("success"));
assertValue(record, "result.actions.0.logging.logged_text", is("_text")); assertValue(record, "result.actions.0.logging.logged_text", is("_text"));
assertValue(record, "_status.actions.log.ack.state", is("ackable")); assertValue(record, "status.actions.log.ack.state", is("ackable"));
} }
public void testExecuteCustomTriggerData() throws Exception { public void testExecuteCustomTriggerData() throws Exception {

View File

@ -48,7 +48,7 @@ public class WatchStatusIntegrationTests extends AbstractWatcherIntegrationTestC
GetResponse getResponse = client().prepareGet(".watches", "watch", "_name").get(); GetResponse getResponse = client().prepareGet(".watches", "watch", "_name").get();
getResponse.getSource(); getResponse.getSource();
XContentSource source = new XContentSource(getResponse.getSourceAsBytesRef(), XContentType.JSON); XContentSource source = new XContentSource(getResponse.getSourceAsBytesRef(), XContentType.JSON);
String lastChecked = source.getValue("_status.last_checked"); String lastChecked = source.getValue("status.last_checked");
assertThat(lastChecked, is(notNullValue())); assertThat(lastChecked, is(notNullValue()));
assertThat(getWatchResponse.getStatus().lastChecked().toString(), is(lastChecked)); assertThat(getWatchResponse.getStatus().lastChecked().toString(), is(lastChecked));

View File

@ -15,7 +15,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
@ -112,6 +112,7 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import org.junit.Before; import org.junit.Before;
import java.io.IOException;
import java.time.Clock; import java.time.Clock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -127,6 +128,7 @@ import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableMap;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds; import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource; import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.xpack.watcher.input.InputBuilders.searchInput; import static org.elasticsearch.xpack.watcher.input.InputBuilders.searchInput;
import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.templateRequest; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.templateRequest;
@ -201,7 +203,7 @@ public class WatchTests extends ESTestCase {
Watch watch = new Watch("_name", trigger, input, condition, transform, throttlePeriod, actions, metadata, watchStatus); Watch watch = new Watch("_name", trigger, input, condition, transform, throttlePeriod, actions, metadata, watchStatus);
BytesReference bytes = XContentFactory.jsonBuilder().value(watch).bytes(); BytesReference bytes = jsonBuilder().value(watch).bytes();
logger.info("{}", bytes.utf8ToString()); logger.info("{}", bytes.utf8ToString());
Watch.Parser watchParser = new Watch.Parser(settings, triggerService, actionRegistry, inputRegistry, null, clock); Watch.Parser watchParser = new Watch.Parser(settings, triggerService, actionRegistry, inputRegistry, null, clock);
@ -220,6 +222,49 @@ public class WatchTests extends ESTestCase {
assertThat(parsedWatch.actions(), equalTo(actions)); assertThat(parsedWatch.actions(), equalTo(actions));
} }
public void testThatBothStatusFieldsCanBeRead() throws Exception {
InputRegistry inputRegistry = mock(InputRegistry.class);
ActionRegistry actionRegistry = mock(ActionRegistry.class);
// a fake trigger service that advances past the trigger end object, which cannot be done with mocking
TriggerService triggerService = new TriggerService(Settings.EMPTY, Collections.emptySet()) {
@Override
public Trigger parseTrigger(String jobName, XContentParser parser) throws IOException {
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
}
return new ScheduleTrigger(randomSchedule());
}
};
DateTime now = new DateTime(UTC);
ClockMock clock = ClockMock.frozen();
clock.setTime(now);
List<ActionWrapper> actions = randomActions();
Map<String, ActionStatus> actionsStatuses = new HashMap<>();
for (ActionWrapper action : actions) {
actionsStatuses.put(action.id(), new ActionStatus(now));
}
WatchStatus watchStatus = new WatchStatus(new DateTime(clock.millis()), unmodifiableMap(actionsStatuses));
Watch.Parser watchParser = new Watch.Parser(settings, triggerService, actionRegistry, inputRegistry, null, clock);
XContentBuilder builder = jsonBuilder().startObject()
.startObject("trigger").endObject();
if (randomBoolean()) {
builder.field("_status", watchStatus);
} else {
builder.field("status", watchStatus);
}
builder.endObject();
Watch watch = watchParser.parse("foo", true, builder.bytes(), XContentType.JSON);
assertThat(watch.status().state().getTimestamp().getMillis(), is(clock.millis()));
for (ActionWrapper action : actions) {
assertThat(watch.status().actionStatus(action.id()), is(actionsStatuses.get(action.id())));
}
}
public void testParserBadActions() throws Exception { public void testParserBadActions() throws Exception {
ClockMock clock = ClockMock.frozen(); ClockMock clock = ClockMock.frozen();
ScheduleRegistry scheduleRegistry = registry(randomSchedule()); ScheduleRegistry scheduleRegistry = registry(randomSchedule());
@ -235,7 +280,7 @@ public class WatchTests extends ESTestCase {
ActionRegistry actionRegistry = registry(actions,conditionRegistry, transformRegistry); ActionRegistry actionRegistry = registry(actions,conditionRegistry, transformRegistry);
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() XContentBuilder jsonBuilder = jsonBuilder()
.startObject() .startObject()
.startArray("actions").endArray() .startArray("actions").endArray()
.endObject(); .endObject();
@ -259,7 +304,7 @@ public class WatchTests extends ESTestCase {
TransformRegistry transformRegistry = transformRegistry(); TransformRegistry transformRegistry = transformRegistry();
ActionRegistry actionRegistry = registry(Collections.emptyList(), conditionRegistry, transformRegistry); ActionRegistry actionRegistry = registry(Collections.emptyList(), conditionRegistry, transformRegistry);
XContentBuilder builder = XContentFactory.jsonBuilder(); XContentBuilder builder = jsonBuilder();
builder.startObject(); builder.startObject();
builder.startObject(Watch.Field.TRIGGER.getPreferredName()) builder.startObject(Watch.Field.TRIGGER.getPreferredName())
.field(ScheduleTrigger.TYPE, schedule(schedule).build()) .field(ScheduleTrigger.TYPE, schedule(schedule).build())
@ -290,7 +335,7 @@ public class WatchTests extends ESTestCase {
WatcherSearchTemplateService searchTemplateService = new WatcherSearchTemplateService(settings, scriptService, xContentRegistry()); WatcherSearchTemplateService searchTemplateService = new WatcherSearchTemplateService(settings, scriptService, xContentRegistry());
XContentBuilder builder = XContentFactory.jsonBuilder(); XContentBuilder builder = jsonBuilder();
builder.startObject(); builder.startObject();
builder.startObject("trigger"); builder.startObject("trigger");

View File

@ -43,14 +43,14 @@
xpack.watcher.ack_watch: xpack.watcher.ack_watch:
watch_id: "my_watch" watch_id: "my_watch"
- match: { "_status.actions.test_index.ack.state" : "awaits_successful_execution" } - match: { "status.actions.test_index.ack.state" : "awaits_successful_execution" }
- do: - do:
search: search:
index: .watches index: .watches
body: { "query": { "term": { "_id": "my_watch" } } } body: { "query": { "term": { "_id": "my_watch" } } }
- match: { hits.total: 1 } - match: { hits.total: 1 }
- match: { hits.hits.0._source._status.actions.test_index.ack.state: "awaits_successful_execution" } - match: { hits.hits.0._source.status.actions.test_index.ack.state: "awaits_successful_execution" }
- do: - do:
xpack.watcher.delete_watch: xpack.watcher.delete_watch:

View File

@ -44,7 +44,7 @@
watch_id: "my_watch" watch_id: "my_watch"
action_id: "test_index" action_id: "test_index"
- match: { "_status.actions.test_index.ack.state" : "awaits_successful_execution" } - match: { "status.actions.test_index.ack.state" : "awaits_successful_execution" }
- do: - do:
xpack.watcher.delete_watch: xpack.watcher.delete_watch:

View File

@ -53,17 +53,17 @@ teardown:
{ {
"record_execution" : true "record_execution" : true
} }
- match: { watch_record._status.actions.indexme.ack.state: "ackable" } - match: { watch_record.status.actions.indexme.ack.state: "ackable" }
- do: - do:
xpack.watcher.ack_watch: xpack.watcher.ack_watch:
watch_id: "my_watch" watch_id: "my_watch"
- match: { "_status.actions.indexme.ack.state" : "acked" } - match: { "status.actions.indexme.ack.state" : "acked" }
- do: - do:
xpack.watcher.get_watch: xpack.watcher.get_watch:
id: "my_watch" id: "my_watch"
- match: { "_status.actions.indexme.ack.state" : "acked" } - match: { "status.actions.indexme.ack.state" : "acked" }
# having a false result will reset the ack state # having a false result will reset the ack state
- do: - do:
@ -79,12 +79,12 @@ teardown:
"indexme" : "force_execute" "indexme" : "force_execute"
} }
} }
- match: { watch_record._status.actions.indexme.ack.state: "awaits_successful_execution" } - match: { watch_record.status.actions.indexme.ack.state: "awaits_successful_execution" }
- do: - do:
xpack.watcher.get_watch: xpack.watcher.get_watch:
id: "my_watch" id: "my_watch"
- match: { "_status.actions.indexme.ack.state" : "awaits_successful_execution" } - match: { "status.actions.indexme.ack.state" : "awaits_successful_execution" }
- do: - do:
xpack.watcher.execute_watch: xpack.watcher.execute_watch:
@ -96,10 +96,10 @@ teardown:
"indexme" : "force_execute" "indexme" : "force_execute"
} }
} }
- match: { watch_record._status.actions.indexme.ack.state: "ackable" } - match: { watch_record.status.actions.indexme.ack.state: "ackable" }
- do: - do:
xpack.watcher.get_watch: xpack.watcher.get_watch:
id: "my_watch" id: "my_watch"
- match: { "_status.actions.indexme.ack.state" : "ackable" } - match: { "status.actions.indexme.ack.state" : "ackable" }

View File

@ -41,40 +41,40 @@
- match: { found : true} - match: { found : true}
- match: { _id: "my_watch" } - match: { _id: "my_watch" }
- match: { _status.state.active: true } - match: { status.state.active: true }
- do: - do:
xpack.watcher.deactivate_watch: xpack.watcher.deactivate_watch:
watch_id: "my_watch" watch_id: "my_watch"
- match: { _status.state.active : false } - match: { status.state.active : false }
- do: - do:
search: search:
index: .watches index: .watches
body: { "query": { "term": { "_id": "my_watch" } } } body: { "query": { "term": { "_id": "my_watch" } } }
- match: { hits.total: 1 } - match: { hits.total: 1 }
- match: { hits.hits.0._source._status.state.active: false } - match: { hits.hits.0._source.status.state.active: false }
- do: - do:
xpack.watcher.get_watch: xpack.watcher.get_watch:
id: "my_watch" id: "my_watch"
- match: { found : true} - match: { found : true}
- match: { _id: "my_watch" } - match: { _id: "my_watch" }
- match: { _status.state.active: false } - match: { status.state.active: false }
- do: - do:
xpack.watcher.activate_watch: xpack.watcher.activate_watch:
watch_id: "my_watch" watch_id: "my_watch"
- match: { _status.state.active : true } - match: { status.state.active : true }
- do: - do:
search: search:
index: .watches index: .watches
body: { "query": { "term": { "_id": "my_watch" } } } body: { "query": { "term": { "_id": "my_watch" } } }
- match: { hits.total: 1 } - match: { hits.total: 1 }
- match: { hits.hits.0._source._status.state.active: true } - match: { hits.hits.0._source.status.state.active: true }
- do: - do:
xpack.watcher.get_watch: xpack.watcher.get_watch:
@ -82,7 +82,7 @@
- match: { found : true} - match: { found : true}
- match: { _id: "my_watch" } - match: { _id: "my_watch" }
- match: { _status.state.active: true } - match: { status.state.active: true }
- do: - do:
xpack.watcher.delete_watch: xpack.watcher.delete_watch:

View File

@ -58,9 +58,9 @@ teardown:
- match: { watch_record.trigger_event.triggered_time: "2012-12-12T12:12:12.120Z" } - match: { watch_record.trigger_event.triggered_time: "2012-12-12T12:12:12.120Z" }
- match: { watch_record.trigger_event.manual.schedule.scheduled_time: "2000-12-12T12:12:12.120Z" } - match: { watch_record.trigger_event.manual.schedule.scheduled_time: "2000-12-12T12:12:12.120Z" }
- match: { watch_record.state: "executed" } - match: { watch_record.state: "executed" }
- match: { watch_record._status.state.active: true } - match: { watch_record.status.state.active: true }
- is_true: watch_record.node - is_true: watch_record.node
- match: { watch_record._status.actions.indexme.ack.state: "ackable" } - match: { watch_record.status.actions.indexme.ack.state: "ackable" }
--- ---
"Test execute watch API with user supplied watch": "Test execute watch API with user supplied watch":
@ -96,8 +96,8 @@ teardown:
- match: { watch_record.watch_id: "_inlined_" } - match: { watch_record.watch_id: "_inlined_" }
- match: { watch_record.trigger_event.type: "manual" } - match: { watch_record.trigger_event.type: "manual" }
- match: { watch_record.state: "executed" } - match: { watch_record.state: "executed" }
- match: { watch_record._status.state.active: true } - match: { watch_record.status.state.active: true }
- match: { watch_record._status.actions.indexme.ack.state: "ackable" } - match: { watch_record.status.actions.indexme.ack.state: "ackable" }
--- ---
"Execute unknown watch results in 404": "Execute unknown watch results in 404":

View File

@ -54,4 +54,4 @@ teardown:
- match: { found : true } - match: { found : true }
- match: { _id: "my_watch" } - match: { _id: "my_watch" }
- match: { _status.state.active: false } - match: { status.state.active: false }

View File

@ -5,7 +5,9 @@
*/ */
package org.elasticsearch.upgrades; package org.elasticsearch.upgrades;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
@ -20,6 +22,7 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.yaml.ObjectPath; import org.elasticsearch.test.rest.yaml.ObjectPath;
import org.elasticsearch.xpack.watcher.condition.AlwaysCondition; import org.elasticsearch.xpack.watcher.condition.AlwaysCondition;
import org.elasticsearch.xpack.watcher.watch.Watch;
import org.junit.Before; import org.junit.Before;
import java.io.IOException; import java.io.IOException;
@ -31,6 +34,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME;
import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.loggingAction; import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder;
@ -104,6 +108,20 @@ public class WatchBackwardsCompatibilityIT extends ESRestTestCase {
} }
@Override
protected boolean preserveIndicesUponCompletion() {
return true;
}
@Override
protected Settings restClientSettings() {
String token = "Basic " + Base64.getEncoder()
.encodeToString("elastic:changeme".getBytes(StandardCharsets.UTF_8));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
public void testWatcherStats() throws Exception { public void testWatcherStats() throws Exception {
executeAgainstAllNodes(client -> executeAgainstAllNodes(client ->
assertOK(client.performRequest("GET", "/_xpack/watcher/stats")) assertOK(client.performRequest("GET", "/_xpack/watcher/stats"))
@ -128,15 +146,12 @@ public class WatchBackwardsCompatibilityIT extends ESRestTestCase {
ContentType.APPLICATION_JSON); ContentType.APPLICATION_JSON);
executeAgainstAllNodes(client -> { executeAgainstAllNodes(client -> {
assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch", fakeUpgradeFrom5x(client);
Collections.emptyMap(), entity));
assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch", Collections.emptyMap(), entity));
assertOK(client.performRequest("GET", "/_xpack/watcher/watch/my-watch")); assertOK(client.performRequest("GET", "/_xpack/watcher/watch/my-watch"));
assertOK(client.performRequest("POST", "/_xpack/watcher/watch/my-watch/_execute")); assertOK(client.performRequest("POST", "/_xpack/watcher/watch/my-watch/_execute"));
assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch/_deactivate")); assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch/_deactivate"));
assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch/_activate")); assertOK(client.performRequest("PUT", "/_xpack/watcher/watch/my-watch/_activate"));
}); });
} }
@ -156,24 +171,26 @@ public class WatchBackwardsCompatibilityIT extends ESRestTestCase {
} }
} }
@Override
protected boolean preserveIndicesUponCompletion() {
return true;
}
@Override
protected Settings restClientSettings() {
String token = "Basic " + Base64.getEncoder()
.encodeToString("elastic:changeme".getBytes(StandardCharsets.UTF_8));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
private void assertOK(Response response) { private void assertOK(Response response) {
assertThat(response.getStatusLine().getStatusCode(), anyOf(equalTo(200), equalTo(201))); assertThat(response.getStatusLine().getStatusCode(), anyOf(equalTo(200), equalTo(201)));
} }
// This is needed for fake the upgrade from 5.x to 6.0, where a new watches template is created, that contains mapping for the status
// field, as _status will be moved to status
// This can be removed once the upgrade API supports everything
private void fakeUpgradeFrom5x(RestClient client) throws IOException {
BytesReference mappingJson = jsonBuilder().startObject().startObject("properties").startObject("status")
.field("type", "object")
.field("enabled", false)
.field("dynamic", true)
.endObject().endObject().endObject()
.bytes();
HttpEntity data = new ByteArrayEntity(mappingJson.toBytesRef().bytes, ContentType.APPLICATION_JSON);
Response response = client.performRequest("PUT", "/" + Watch.INDEX + "/_mapping/" + Watch.DOC_TYPE, Collections.emptyMap(), data);
assertOK(response);
}
private Nodes buildNodeAndVersions() throws IOException { private Nodes buildNodeAndVersions() throws IOException {
Response response = client().performRequest("GET", "_nodes"); Response response = client().performRequest("GET", "_nodes");
ObjectPath objectPath = ObjectPath.createFromResponse(response); ObjectPath objectPath = ObjectPath.createFromResponse(response);