/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.linkedin.pinot.controller.api.restlet.resources;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linkedin.pinot.common.config.TableNameBuilder;
import com.linkedin.pinot.common.restlet.swagger.HttpVerb;
import com.linkedin.pinot.common.restlet.swagger.Parameter;
import com.linkedin.pinot.common.restlet.swagger.Paths;
import com.linkedin.pinot.common.restlet.swagger.Summary;
import com.linkedin.pinot.common.restlet.swagger.Tags;
import com.linkedin.pinot.common.utils.CommonConstants;
import com.linkedin.pinot.common.utils.CommonConstants.Helix.TableType;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.helix.model.ExternalView;
import org.apache.helix.model.IdealState;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.Get;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TableViews extends BasePinotControllerRestletResource {
private static final Logger LOGGER = LoggerFactory.getLogger(TableViews.class);
public static final String IDEALSTATE = "idealstate";
public static final String EXTERNALVIEW = "externalview";
@Get
@Override
public Representation get() {
final String tableName = (String) getRequest().getAttributes().get("tableName");
final int viewPositon = 3;
String[] path = getReference().getPath().split("/");
// first part is "" because paths start with /
if (path.length < (viewPositon + 1) ||
(!path[viewPositon].equalsIgnoreCase(EXTERNALVIEW) && !path[viewPositon].equalsIgnoreCase(IDEALSTATE))) {
// this is unexpected condition
LOGGER.error("Invalid path: {} while reading views", path);
return responseRepresentation(Status.SERVER_ERROR_INTERNAL, "{\"error\":\"Invalid reqeust path\"");
}
final String view = path[viewPositon];
String tableTypeStr = getQuery().getValues("tableType");
if (tableTypeStr == null) {
tableTypeStr = "";
}
TableType tableType = null;
if (! tableTypeStr.isEmpty()) {
try {
tableType = TableType.valueOf(tableTypeStr.toUpperCase());
} catch(IllegalArgumentException e) {
return responseRepresentation(Status.CLIENT_ERROR_BAD_REQUEST,
"{\"error\":\"Illegal value for table type: " + tableTypeStr + "\"}");
} catch (Exception e) {
LOGGER.error("Error", e);
return responseRepresentation(Status.SERVER_ERROR_INTERNAL, "error");
}
}
if (tableName == null || view == null) {
return responseRepresentation(Status.CLIENT_ERROR_BAD_REQUEST,
"{\"error\" : \"Table name and view type are required\"}");
}
return getTableState(tableName, view, tableType);
}
public static class TableView
{
@JsonProperty("OFFLINE")
Map<String, Map<String, String>> offline;
@JsonProperty("REALTIME")
Map<String, Map<String, String>> realtime;
}
// we use name "view" to closely match underlying names and to not
// confuse with table state of enable/disable
private Representation getTableState(
String tableName,
String view,
TableType tableType) {
TableView tableView;
if (view.equalsIgnoreCase(IDEALSTATE)) {
tableView = getTableIdealState(tableName, tableType);
} else if (view.equalsIgnoreCase(EXTERNALVIEW)) {
tableView = getTableExternalView(tableName, tableType);
} else {
return responseRepresentation(Status.CLIENT_ERROR_BAD_REQUEST,
"{\"error\" : \"Bad view name: " + view + ". Expected idealstate or externalview\" }");
}
if (tableView.offline == null && tableView.realtime == null) {
return responseRepresentation(Status.CLIENT_ERROR_NOT_FOUND,
"{\"error\" : \"Table not found}");
}
ObjectMapper mapper = new ObjectMapper();
try {
String response = mapper.writeValueAsString(tableView);
return responseRepresentation(Status.SUCCESS_OK, response);
} catch (JsonProcessingException e) {
LOGGER.error("Failed to serialize {} for table {}", tableName, view, e);
return responseRepresentation(Status.SERVER_ERROR_INTERNAL, "{\"error\": \"Error serializing response\"}");
}
}
@HttpVerb("get")
@Summary("Get table external view")
@Tags({"table"})
@Paths({"/tables/{tableName}/externalview"})
private TableView getTableExternalView(
@Parameter(name = "tableName", in = "path", description = "Table name(without type)", required = true)
@Nonnull String tableNameOptType,
@Parameter(name = "tableType", in="query", description = "Table type", required = false)
@Nullable TableType tableType) {
TableView tableView = new TableView();
if (tableType == null || tableType == TableType.OFFLINE) {
tableView.offline = getExternalView(tableNameOptType, TableType.OFFLINE);
}
if (tableType == null || tableType == TableType.REALTIME) {
tableView.realtime = getExternalView(tableNameOptType, TableType.REALTIME);
}
return tableView;
}
@HttpVerb("get")
@Summary("Get table idealstate")
@Tags({"table"})
@Paths({"/tables/{tableName}/idealstate"})
private TableView getTableIdealState(
@Parameter(name = "tableName", in = "path", description = "Table name(without type)", required = true)
String tableNameOptType,
@Parameter(name = "tableType", in="query", description = "Table type", required = false)
TableType tableType) {
TableView tableView = new TableView();
if (tableType == null || tableType == TableType.OFFLINE) {
tableView.offline = getIdealState(tableNameOptType, TableType.OFFLINE);
}
if (tableType == null || tableType == TableType.REALTIME) {
tableView.realtime = getIdealState(tableNameOptType, TableType.REALTIME);
}
return tableView;
}
@Nullable
public Map<String, Map<String, String>> getIdealState(@Nonnull String tableNameOptType,
@Parameter(name = "tableType", in = "query", description = "Table type", required = false) @Nullable TableType tableType) {
String tableNameWithType = getTableNameWithType(tableNameOptType, tableType);
IdealState resourceIdealState = _pinotHelixResourceManager.getHelixAdmin()
.getResourceIdealState(_pinotHelixResourceManager.getHelixClusterName(), tableNameWithType);
return resourceIdealState == null ? null : resourceIdealState.getRecord().getMapFields();
}
@Nullable
public Map<String, Map<String, String>> getExternalView(@Nonnull String tableNameOptType, TableType tableType) {
String tableNameWithType = getTableNameWithType(tableNameOptType, tableType);
ExternalView resourceEV = _pinotHelixResourceManager.getHelixAdmin()
.getResourceExternalView(_pinotHelixResourceManager.getHelixClusterName(), tableNameWithType);
return resourceEV == null ? null : resourceEV.getRecord().getMapFields();
}
private String getTableNameWithType(@Nonnull String tableNameOptType, @Nullable TableType tableType) {
if (tableType != null) {
if (tableType == CommonConstants.Helix.TableType.OFFLINE) {
return TableNameBuilder.OFFLINE.tableNameWithType(tableNameOptType);
} else {
return TableNameBuilder.REALTIME.tableNameWithType(tableNameOptType);
}
} else {
return tableNameOptType;
}
}
}