Skip to content

Commit

Permalink
Implement GET API of ip2geo datasource
Browse files Browse the repository at this point in the history
Signed-off-by: Heemin Kim <heemin@amazon.com>
  • Loading branch information
heemin32 committed Apr 19, 2023
1 parent 0962e9a commit 56fa87f
Show file tree
Hide file tree
Showing 18 changed files with 575 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.geospatial.ip2geo.action;

import org.opensearch.action.ActionType;

/**
* Ip2Geo datasource get action
*/
public class GetDatasourceAction extends ActionType<GetDatasourceResponse> {
/**
* Get datasource action instance
*/
public static final GetDatasourceAction INSTANCE = new GetDatasourceAction();
/**
* Name of a get datasource action
*/
public static final String NAME = "cluster:admin/geospatial/datasource/get";

private GetDatasourceAction() {
super(NAME, GetDatasourceResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.geospatial.ip2geo.action;

import java.io.IOException;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;

/**
* GeoIP datasource get request
*/
@Getter
@Setter
@Log4j2
public class GetDatasourceRequest extends ActionRequest {
/**
* @param names the datasource names
* @return the datasource names
*/
private String[] names;

/**
* Constructs a new get datasource request with a list of datasources.
* <p>
* If the list of datasources is empty or it contains a single element "_all", all registered datasources
* are returned.
*
* @param names list of datasource names
*/
public GetDatasourceRequest(final String[] names) {
this.names = names;
}

/**
* Constructor with stream input
* @param in the stream input
* @throws IOException IOException
*/
public GetDatasourceRequest(final StreamInput in) throws IOException {
super(in);
this.names = in.readStringArray();
}

@Override
public ActionRequestValidationException validate() {
return null;
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringArray(names);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.geospatial.ip2geo.action;

import java.io.IOException;
import java.time.Instant;
import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import org.opensearch.action.ActionResponse;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.geospatial.ip2geo.jobscheduler.Datasource;

/**
* GeoIP datasource get request
*/
@Getter
@Setter
@Log4j2
public class GetDatasourceResponse extends ActionResponse implements ToXContentObject {
private final String FIELD_NAME_DATASOURCES = "datasources";
private final String FIELD_NAME_NAME = "name";
private final String FIELD_NAME_STATE = "state";
private final String FIELD_NAME_ENDPOINT = "endpoint";
private final String FIELD_NAME_UPDATE_INTERVAL = "update_interval_in_days";
private final String FIELD_NAME_NEXT_UPDATE_AT = "next_update_at_in_epoch_millis";
private final String FIELD_NAME_NEXT_UPDATE_AT_READABLE = "next_update_at";
private final String FIELD_NAME_DATABASE = "database";
private final String FIELD_NAME_UPDATE_STATS = "update_stats";
private List<Datasource> datasources;

/**
* Default constructor
*
* @param datasources List of datasources
*/
public GetDatasourceResponse(final List<Datasource> datasources) {
this.datasources = datasources;
}

/**
* Constructor with StreamInput
*
* @param in the stream input
*/
public GetDatasourceResponse(final StreamInput in) throws IOException {
in.readList(Datasource::new);
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
out.writeList(datasources);
}

@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
builder.startArray(FIELD_NAME_DATASOURCES);
for (Datasource datasource : datasources) {
builder.startObject();
builder.field(FIELD_NAME_NAME, datasource.getName());
builder.field(FIELD_NAME_STATE, datasource.getState());
builder.field(FIELD_NAME_ENDPOINT, datasource.getEndpoint());
builder.field(FIELD_NAME_UPDATE_INTERVAL, datasource.getSchedule().getInterval());
builder.timeField(
FIELD_NAME_NEXT_UPDATE_AT,
FIELD_NAME_NEXT_UPDATE_AT_READABLE,
datasource.getSchedule().getNextExecutionTime(Instant.now()).toEpochMilli()
);
builder.field(FIELD_NAME_DATABASE, datasource.getDatabase());
builder.field(FIELD_NAME_UPDATE_STATS, datasource.getUpdateStats());
builder.endObject();
}
builder.endArray();
builder.endObject();
return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.geospatial.ip2geo.action;

import java.util.List;

import lombok.extern.log4j.Log4j2;

import org.opensearch.action.ActionListener;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.HandledTransportAction;
import org.opensearch.client.Client;
import org.opensearch.common.inject.Inject;
import org.opensearch.geospatial.ip2geo.common.DatasourceHelper;
import org.opensearch.geospatial.ip2geo.jobscheduler.Datasource;
import org.opensearch.tasks.Task;
import org.opensearch.transport.TransportService;

/**
* Transport action to get datasource
*/
@Log4j2
public class GetDatasourceTransportAction extends HandledTransportAction<GetDatasourceRequest, GetDatasourceResponse> {
private final Client client;

/**
* Default constructor
* @param transportService the transport service
* @param actionFilters the action filters
* @param client the client
*/
@Inject
public GetDatasourceTransportAction(final TransportService transportService, final ActionFilters actionFilters, final Client client) {
super(GetDatasourceAction.NAME, transportService, actionFilters, GetDatasourceRequest::new);
this.client = client;
}

@Override
protected void doExecute(final Task task, final GetDatasourceRequest request, final ActionListener<GetDatasourceResponse> listener) {
if (request.getNames().length == 0 || (request.getNames().length == 1 && "_all".equals(request.getNames()[0]))) {
// Don't expect too many data sources. Therefore, querying all data sources without pagination should be fine.
DatasourceHelper.getAllDatasources(client, new ActionListener<>() {
@Override
public void onResponse(final List<Datasource> datasources) {
listener.onResponse(new GetDatasourceResponse(datasources));
}

@Override
public void onFailure(final Exception e) {
listener.onFailure(e);
}
});
} else {
DatasourceHelper.getDatasources(client, request.getNames(), new ActionListener<>() {
@Override
public void onResponse(final List<Datasource> datasources) {
listener.onResponse(new GetDatasourceResponse(datasources));
}

@Override
public void onFailure(final Exception e) {
listener.onFailure(e);
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.opensearch.action.support.master.AcknowledgedResponse;

/**
* GeoIp datasource creation action
* Ip2Geo datasource creation action
*/
public class PutDatasourceAction extends ActionType<AcknowledgedResponse> {
/**
Expand All @@ -22,7 +22,7 @@ public class PutDatasourceAction extends ActionType<AcknowledgedResponse> {
/**
* Name of a put datasource action
*/
public static final String NAME = "cluster:admin/geospatial/datasource";
public static final String NAME = "cluster:admin/geospatial/datasource/put";

private PutDatasourceAction() {
super(NAME, AcknowledgedResponse::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.opensearch.geospatial.ip2geo.action;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
Expand All @@ -18,8 +19,10 @@
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import org.opensearch.OpenSearchException;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.action.support.master.AcknowledgedRequest;
import org.opensearch.common.Strings;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.unit.TimeValue;
Expand All @@ -36,11 +39,12 @@
public class PutDatasourceRequest extends AcknowledgedRequest<PutDatasourceRequest> {
private static final ParseField ENDPOINT_FIELD = new ParseField("endpoint");
private static final ParseField UPDATE_INTERVAL_IN_DAYS_FIELD = new ParseField("update_interval_in_days");
private static final int MAX_DATASOURCE_NAME_BYTES = 255;
/**
* @param datasourceName the datasource name
* @param name the datasource name
* @return the datasource name
*/
private String datasourceName;
private String name;
/**
* @param endpoint url to a manifest file for a datasource
* @return url to a manifest file for a datasource
Expand All @@ -64,10 +68,10 @@ public class PutDatasourceRequest extends AcknowledgedRequest<PutDatasourceReque

/**
* Default constructor
* @param datasourceName name of a datasource
* @param name name of a datasource
*/
public PutDatasourceRequest(final String datasourceName) {
this.datasourceName = datasourceName;
public PutDatasourceRequest(final String name) {
this.name = name;
}

/**
Expand All @@ -77,27 +81,59 @@ public PutDatasourceRequest(final String datasourceName) {
*/
public PutDatasourceRequest(final StreamInput in) throws IOException {
super(in);
this.datasourceName = in.readString();
this.name = in.readString();
this.endpoint = in.readString();
this.updateIntervalInDays = in.readTimeValue();
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(datasourceName);
out.writeString(name);
out.writeString(endpoint);
out.writeTimeValue(updateIntervalInDays);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException errors = new ActionRequestValidationException();
validateDatasourceName(errors);
validateEndpoint(errors);
validateUpdateInterval(errors);
return errors.validationErrors().isEmpty() ? null : errors;
}

private void validateDatasourceName(final ActionRequestValidationException errors) {
if (!Strings.validFileName(name)) {
errors.addValidationError("Datasource name must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
}
if (name.isEmpty()) {
errors.addValidationError("Datasource name must not be empty");
}
if (name.contains("#")) {
errors.addValidationError("Datasource name must not contain '#'");
}
if (name.contains(":")) {
errors.addValidationError("Datasource name must not contain ':'");
}
if (name.charAt(0) == '_' || name.charAt(0) == '-' || name.charAt(0) == '+') {
errors.addValidationError("Datasource name must not start with '_', '-', or '+'");
}
int byteCount = 0;
try {
byteCount = name.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
// UTF-8 should always be supported, but rethrow this if it is not for some reason
throw new OpenSearchException("Unable to determine length of datasource name", e);
}
if (byteCount > MAX_DATASOURCE_NAME_BYTES) {
errors.addValidationError("Datasource name is too long, (" + byteCount + " > " + MAX_DATASOURCE_NAME_BYTES + ")");
}
if (name.equals(".") || name.equals("..")) {
errors.addValidationError("Datasource name must not be '.' or '..'");
}
}

private void validateEndpoint(final ActionRequestValidationException errors) {
try {
URL url = new URL(endpoint);
Expand Down
Loading

0 comments on commit 56fa87f

Please sign in to comment.