-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add custom metadata to snapshots #41281
Changes from 32 commits
a3f3974
f7d9e10
26b1457
62d2c03
4d6ea78
7ce8974
2337d06
b727379
84b39fd
7ab2b70
30a44c4
b098c7d
4136cd2
e44ebf7
20f23ea
0793c32
0abbf35
a265a2d
a6ab70c
337d163
726ef88
1638898
b717b69
4541985
31b48f7
b8592ea
eeadd65
348d6bd
0f4f9fd
61588bb
dc8f0b2
fe70c1e
d9d4388
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -349,7 +349,11 @@ PUT /_snapshot/my_backup/snapshot_2?wait_for_completion=true | |
{ | ||
"indices": "index_1,index_2", | ||
"ignore_unavailable": true, | ||
"include_global_state": false | ||
"include_global_state": false, | ||
"_meta": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should somehow limit the size of this? :) It seems to me there's quite a bit of potential for misusing this. Not sure what others think, maybe @ywelsch has an opinion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the size of meta should be limited There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That does seem like a good idea - I don't see limiting the number of fields being terribly useful, perhaps a limit on the number of bytes in serialized-to-JSON form? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gibrown yea limiting the size in bytes makes sense imo. |
||
"taken_by": "kimchy", | ||
"taken_because": "backup before upgrading" | ||
} | ||
} | ||
----------------------------------- | ||
// CONSOLE | ||
|
@@ -363,6 +367,9 @@ By setting `include_global_state` to false it's possible to prevent the cluster | |
the snapshot. By default, the entire snapshot will fail if one or more indices participating in the snapshot don't have | ||
all primary shards available. This behaviour can be changed by setting `partial` to `true`. | ||
|
||
The `_meta` field can be used to attach arbitrary metadata to the snapshot. This may be a record of who took the snapshot, | ||
why it was taken, or any other data that might be useful. | ||
|
||
Snapshot names can be automatically derived using <<date-math-index-names,date math expressions>>, similarly as when creating | ||
new indices. Note that special characters need to be URI encoded. | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -19,12 +19,14 @@ | |||
|
||||
package org.elasticsearch.action.admin.cluster.snapshots.create; | ||||
|
||||
import org.elasticsearch.ElasticsearchException; | ||||
import org.elasticsearch.ElasticsearchGenerationException; | ||||
import org.elasticsearch.action.ActionRequestValidationException; | ||||
import org.elasticsearch.action.IndicesRequest; | ||||
import org.elasticsearch.action.support.IndicesOptions; | ||||
import org.elasticsearch.action.support.master.MasterNodeRequest; | ||||
import org.elasticsearch.common.Strings; | ||||
import org.elasticsearch.common.bytes.BytesReference; | ||||
import org.elasticsearch.common.io.stream.StreamInput; | ||||
import org.elasticsearch.common.io.stream.StreamOutput; | ||||
import org.elasticsearch.common.settings.Settings; | ||||
|
@@ -46,6 +48,7 @@ | |||
import static org.elasticsearch.common.settings.Settings.readSettingsFromStream; | ||||
import static org.elasticsearch.common.settings.Settings.writeSettingsToStream; | ||||
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue; | ||||
import static org.elasticsearch.snapshots.SnapshotInfo.METADATA_FIELD_INTRODUCED; | ||||
|
||||
/** | ||||
* Create snapshot request | ||||
|
@@ -63,6 +66,7 @@ | |||
*/ | ||||
public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotRequest> | ||||
implements IndicesRequest.Replaceable, ToXContentObject { | ||||
public static int MAXIMUM_METADATA_BYTES = 1024; // chosen arbitrarily | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we test it somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, in Line 122 in eeadd65
|
||||
|
||||
private String snapshot; | ||||
|
||||
|
@@ -80,6 +84,8 @@ public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotReque | |||
|
||||
private boolean waitForCompletion; | ||||
|
||||
private Map<String, Object> userMetadata; | ||||
|
||||
public CreateSnapshotRequest() { | ||||
} | ||||
|
||||
|
@@ -104,6 +110,9 @@ public CreateSnapshotRequest(StreamInput in) throws IOException { | |||
includeGlobalState = in.readBoolean(); | ||||
waitForCompletion = in.readBoolean(); | ||||
partial = in.readBoolean(); | ||||
if (in.getVersion().onOrAfter(METADATA_FIELD_INTRODUCED)) { | ||||
userMetadata = in.readMap(); | ||||
} | ||||
} | ||||
|
||||
@Override | ||||
|
@@ -117,6 +126,9 @@ public void writeTo(StreamOutput out) throws IOException { | |||
out.writeBoolean(includeGlobalState); | ||||
out.writeBoolean(waitForCompletion); | ||||
out.writeBoolean(partial); | ||||
if (out.getVersion().onOrAfter(METADATA_FIELD_INTRODUCED)) { | ||||
out.writeMap(userMetadata); | ||||
} | ||||
} | ||||
|
||||
@Override | ||||
|
@@ -144,9 +156,26 @@ public ActionRequestValidationException validate() { | |||
if (settings == null) { | ||||
validationException = addValidationError("settings is null", validationException); | ||||
} | ||||
if (isMetadataTooBig(userMetadata)) { | ||||
validationException = addValidationError("metadata must be smaller than 1024 bytes", validationException); | ||||
} | ||||
return validationException; | ||||
} | ||||
|
||||
private static boolean isMetadataTooBig(Map<String, Object> userMetadata) { | ||||
if (userMetadata == null) { | ||||
return false; | ||||
} | ||||
try (XContentBuilder builder = XContentFactory.jsonBuilder()) { | ||||
builder.value(userMetadata); | ||||
int size = BytesReference.bytes(builder).length(); | ||||
return size > MAXIMUM_METADATA_BYTES; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could just return the size of the meta here and then do the check upstream? That way we could add the serialized size of the metadata to the exception which might be helpful when debugging things in the future? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've made this change, it's a good suggestion. |
||||
} catch (IOException e) { | ||||
// This should not be possible as we are just rendering the xcontent in memory | ||||
throw new ElasticsearchException(e); | ||||
} | ||||
} | ||||
|
||||
/** | ||||
* Sets the snapshot name | ||||
* | ||||
|
@@ -378,6 +407,15 @@ public boolean includeGlobalState() { | |||
return includeGlobalState; | ||||
} | ||||
|
||||
public Map<String, Object> userMetadata() { | ||||
return userMetadata; | ||||
} | ||||
|
||||
public CreateSnapshotRequest userMetadata(Map<String, Object> userMetadata) { | ||||
this.userMetadata = userMetadata; | ||||
return this; | ||||
} | ||||
|
||||
/** | ||||
* Parses snapshot definition. | ||||
* | ||||
|
@@ -405,6 +443,11 @@ public CreateSnapshotRequest source(Map<String, Object> source) { | |||
settings((Map<String, Object>) entry.getValue()); | ||||
} else if (name.equals("include_global_state")) { | ||||
includeGlobalState = nodeBooleanValue(entry.getValue(), "include_global_state"); | ||||
} else if (name.equals("metadata")) { | ||||
if (entry.getValue() != null && (entry.getValue() instanceof Map == false)) { | ||||
throw new IllegalArgumentException("malformed metadata, should be an object"); | ||||
} | ||||
userMetadata((Map<String, Object>) entry.getValue()); | ||||
} | ||||
} | ||||
indicesOptions(IndicesOptions.fromMap(source, indicesOptions)); | ||||
|
@@ -433,6 +476,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws | |||
if (indicesOptions != null) { | ||||
indicesOptions.toXContent(builder, params); | ||||
} | ||||
builder.field("metadata", userMetadata); | ||||
builder.endObject(); | ||||
return builder; | ||||
} | ||||
|
@@ -460,12 +504,14 @@ public boolean equals(Object o) { | |||
Arrays.equals(indices, that.indices) && | ||||
Objects.equals(indicesOptions, that.indicesOptions) && | ||||
Objects.equals(settings, that.settings) && | ||||
Objects.equals(masterNodeTimeout, that.masterNodeTimeout); | ||||
Objects.equals(masterNodeTimeout, that.masterNodeTimeout) && | ||||
Objects.equals(userMetadata, that.userMetadata); | ||||
} | ||||
|
||||
@Override | ||||
public int hashCode() { | ||||
int result = Objects.hash(snapshot, repository, indicesOptions, partial, settings, includeGlobalState, waitForCompletion); | ||||
int result = Objects.hash(snapshot, repository, indicesOptions, partial, settings, includeGlobalState, | ||||
waitForCompletion, userMetadata); | ||||
result = 31 * result + Arrays.hashCode(indices); | ||||
return result; | ||||
} | ||||
|
@@ -482,6 +528,7 @@ public String toString() { | |||
", includeGlobalState=" + includeGlobalState + | ||||
", waitForCompletion=" + waitForCompletion + | ||||
", masterNodeTimeout=" + masterNodeTimeout + | ||||
", metadata=" + userMetadata + | ||||
'}'; | ||||
} | ||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to avoid copy-pasting metadata creation logic, I'd instead refactor out createSnapshotRequest method that accepts a boolean whether to include metadata to it.