Skip to content

Commit e0c3b07

Browse files
authored
Implement datasource update API (#292)
Signed-off-by: Heemin Kim <heemin@amazon.com>
1 parent 3384fb4 commit e0c3b07

13 files changed

+925
-12
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.geospatial.ip2geo.action;
7+
8+
import static org.opensearch.geospatial.shared.URLBuilder.URL_DELIMITER;
9+
import static org.opensearch.geospatial.shared.URLBuilder.getPluginURLPrefix;
10+
import static org.opensearch.rest.RestRequest.Method.PUT;
11+
12+
import java.io.IOException;
13+
import java.util.List;
14+
15+
import org.opensearch.client.node.NodeClient;
16+
import org.opensearch.core.xcontent.XContentParser;
17+
import org.opensearch.rest.BaseRestHandler;
18+
import org.opensearch.rest.RestRequest;
19+
import org.opensearch.rest.action.RestToXContentListener;
20+
21+
/**
22+
* Rest handler for Ip2Geo datasource update request
23+
*/
24+
public class RestUpdateDatasourceHandler extends BaseRestHandler {
25+
private static final String ACTION_NAME = "ip2geo_datasource_update";
26+
27+
@Override
28+
public String getName() {
29+
return ACTION_NAME;
30+
}
31+
32+
@Override
33+
protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
34+
final PutDatasourceRequest putDatasourceRequest = new PutDatasourceRequest(request.param("name"));
35+
if (request.hasContentOrSourceParam()) {
36+
try (XContentParser parser = request.contentOrSourceParamParser()) {
37+
PutDatasourceRequest.PARSER.parse(parser, putDatasourceRequest, null);
38+
}
39+
}
40+
return channel -> client.executeLocally(
41+
UpdateDatasourceAction.INSTANCE,
42+
putDatasourceRequest,
43+
new RestToXContentListener<>(channel)
44+
);
45+
}
46+
47+
@Override
48+
public List<Route> routes() {
49+
String path = String.join(URL_DELIMITER, getPluginURLPrefix(), "ip2geo/datasource/{name}/_settings");
50+
return List.of(new Route(PUT, path));
51+
}
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.geospatial.ip2geo.action;
7+
8+
import org.opensearch.action.ActionType;
9+
import org.opensearch.action.support.master.AcknowledgedResponse;
10+
11+
/**
12+
* Ip2Geo datasource update action
13+
*/
14+
public class UpdateDatasourceAction extends ActionType<AcknowledgedResponse> {
15+
/**
16+
* Update datasource action instance
17+
*/
18+
public static final UpdateDatasourceAction INSTANCE = new UpdateDatasourceAction();
19+
/**
20+
* Update datasource action name
21+
*/
22+
public static final String NAME = "cluster:admin/geospatial/datasource/update";
23+
24+
private UpdateDatasourceAction() {
25+
super(NAME, AcknowledgedResponse::new);
26+
}
27+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.geospatial.ip2geo.action;
7+
8+
import java.io.IOException;
9+
import java.net.MalformedURLException;
10+
import java.net.URISyntaxException;
11+
import java.net.URL;
12+
import java.util.Locale;
13+
14+
import lombok.EqualsAndHashCode;
15+
import lombok.Getter;
16+
import lombok.Setter;
17+
import lombok.extern.log4j.Log4j2;
18+
19+
import org.opensearch.action.ActionRequestValidationException;
20+
import org.opensearch.action.support.master.AcknowledgedRequest;
21+
import org.opensearch.common.io.stream.StreamInput;
22+
import org.opensearch.common.io.stream.StreamOutput;
23+
import org.opensearch.common.unit.TimeValue;
24+
import org.opensearch.core.ParseField;
25+
import org.opensearch.core.xcontent.ObjectParser;
26+
import org.opensearch.geospatial.ip2geo.common.DatasourceManifest;
27+
28+
/**
29+
* Ip2Geo datasource update request
30+
*/
31+
@Getter
32+
@Setter
33+
@Log4j2
34+
@EqualsAndHashCode(callSuper = false)
35+
public class UpdateDatasourceRequest extends AcknowledgedRequest<UpdateDatasourceRequest> {
36+
private static final ParseField ENDPOINT_FIELD = new ParseField("endpoint");
37+
private static final ParseField UPDATE_INTERVAL_IN_DAYS_FIELD = new ParseField("update_interval_in_days");
38+
private static final int MAX_DATASOURCE_NAME_BYTES = 255;
39+
/**
40+
* @param name the datasource name
41+
* @return the datasource name
42+
*/
43+
private String name;
44+
/**
45+
* @param endpoint url to a manifest file for a datasource
46+
* @return url to a manifest file for a datasource
47+
*/
48+
private String endpoint;
49+
/**
50+
* @param updateInterval update interval of a datasource
51+
* @return update interval of a datasource
52+
*/
53+
private TimeValue updateInterval;
54+
55+
/**
56+
* Parser of a datasource
57+
*/
58+
public static final ObjectParser<UpdateDatasourceRequest, Void> PARSER;
59+
static {
60+
PARSER = new ObjectParser<>("update_datasource");
61+
PARSER.declareString((request, val) -> request.setEndpoint(val), ENDPOINT_FIELD);
62+
PARSER.declareLong((request, val) -> request.setUpdateInterval(TimeValue.timeValueDays(val)), UPDATE_INTERVAL_IN_DAYS_FIELD);
63+
}
64+
65+
/**
66+
* Constructor
67+
* @param name name of a datasource
68+
*/
69+
public UpdateDatasourceRequest(final String name) {
70+
this.name = name;
71+
}
72+
73+
/**
74+
* Constructor
75+
* @param in the stream input
76+
* @throws IOException IOException
77+
*/
78+
public UpdateDatasourceRequest(final StreamInput in) throws IOException {
79+
super(in);
80+
this.name = in.readString();
81+
this.endpoint = in.readOptionalString();
82+
this.updateInterval = in.readOptionalTimeValue();
83+
}
84+
85+
@Override
86+
public void writeTo(final StreamOutput out) throws IOException {
87+
super.writeTo(out);
88+
out.writeString(name);
89+
out.writeOptionalString(endpoint);
90+
out.writeOptionalTimeValue(updateInterval);
91+
}
92+
93+
@Override
94+
public ActionRequestValidationException validate() {
95+
ActionRequestValidationException errors = new ActionRequestValidationException();
96+
if (endpoint == null && updateInterval == null) {
97+
errors.addValidationError("no values to update");
98+
}
99+
100+
validateEndpoint(errors);
101+
validateUpdateInterval(errors);
102+
103+
return errors.validationErrors().isEmpty() ? null : errors;
104+
}
105+
106+
/**
107+
* Conduct following validation on endpoint
108+
* 1. endpoint format complies with RFC-2396
109+
* 2. validate manifest file from the endpoint
110+
*
111+
* @param errors the errors to add error messages
112+
*/
113+
private void validateEndpoint(final ActionRequestValidationException errors) {
114+
if (endpoint == null) {
115+
return;
116+
}
117+
118+
try {
119+
URL url = new URL(endpoint);
120+
url.toURI(); // Validate URL complies with RFC-2396
121+
validateManifestFile(url, errors);
122+
} catch (MalformedURLException | URISyntaxException e) {
123+
log.info("Invalid URL[{}] is provided", endpoint, e);
124+
errors.addValidationError("Invalid URL format is provided");
125+
}
126+
}
127+
128+
/**
129+
* Conduct following validation on url
130+
* 1. can read manifest file from the endpoint
131+
* 2. the url in the manifest file complies with RFC-2396
132+
*
133+
* @param url the url to validate
134+
* @param errors the errors to add error messages
135+
*/
136+
private void validateManifestFile(final URL url, final ActionRequestValidationException errors) {
137+
DatasourceManifest manifest;
138+
try {
139+
manifest = DatasourceManifest.Builder.build(url);
140+
} catch (Exception e) {
141+
log.info("Error occurred while reading a file from {}", url, e);
142+
errors.addValidationError(String.format(Locale.ROOT, "Error occurred while reading a file from %s: %s", url, e.getMessage()));
143+
return;
144+
}
145+
146+
try {
147+
new URL(manifest.getUrl()).toURI(); // Validate URL complies with RFC-2396
148+
} catch (MalformedURLException | URISyntaxException e) {
149+
log.info("Invalid URL[{}] is provided for url field in the manifest file", manifest.getUrl(), e);
150+
errors.addValidationError("Invalid URL format is provided for url field in the manifest file");
151+
}
152+
}
153+
154+
/**
155+
* Validate updateInterval is equal or larger than 1
156+
*
157+
* @param errors the errors to add error messages
158+
*/
159+
private void validateUpdateInterval(final ActionRequestValidationException errors) {
160+
if (updateInterval == null) {
161+
return;
162+
}
163+
164+
if (updateInterval.compareTo(TimeValue.timeValueDays(1)) < 0) {
165+
errors.addValidationError("Update interval should be equal to or larger than 1 day");
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)