blob: cf131b24ea26f7d6f97b57b8c2d4fb690d9fdcdc [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 org.apache.impala.analysis;
import java.util.ArrayList;
import java.util.List;
import org.apache.impala.authorization.AuthorizationConfig;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.common.InternalException;
import org.apache.impala.common.Pair;
import org.apache.impala.thrift.TAdminRequest;
import org.apache.impala.thrift.TAdminRequestType;
import org.apache.impala.thrift.TNetworkAddress;
import org.apache.impala.thrift.TShutdownParams;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
/**
* Represents an administrative function call, e.g. ": shutdown('hostname:123')".
*
* This "admin statement" framework provides a way to expand the set of supported admin
* statements without modifying the SQL grammar. For now, the only supported function is
* shutdown(), so the logic in here is not generic.
*/
public class AdminFnStmt extends StatementBase {
// Name of the function. Validated during analysis.
private final String fnName_;
// Arguments to the function. Always non-null.
private final List<Expr> params_;
// Parameters for the shutdown() command.
// Address of the backend to shut down, If 'backend_' is null, that means the current
// server. If 'backend_.port' is 0, we assume the backend has the same port as this
// impalad.
private TNetworkAddress backend_;
// Deadline in seconds. -1 if no deadline specified.
private long deadlineSecs_;
public AdminFnStmt(String fnName, List<Expr> params) {
this.fnName_ = fnName;
this.params_ = params;
}
@Override
public String toSql(ToSqlOptions options) {
StringBuilder sb = new StringBuilder();
sb.append(":").append(fnName_).append("(");
List<String> paramsSql = new ArrayList<>();
for (Expr param : params_) paramsSql.add(param.toSql(options));
sb.append(Joiner.on(", ").join(paramsSql));
sb.append(")");
return sb.toString();
}
public TAdminRequest toThrift() throws InternalException {
TAdminRequest result = new TAdminRequest();
result.type = TAdminRequestType.SHUTDOWN;
result.shutdown_params = new TShutdownParams();
if (backend_ != null) result.shutdown_params.setBackend(backend_);
if (deadlineSecs_ != -1) result.shutdown_params.setDeadline_s(deadlineSecs_);
return result;
}
@Override
public void analyze(Analyzer analyzer) throws AnalysisException {
super.analyze(analyzer);
for (Expr param : params_) param.analyze(analyzer);
// Only shutdown is supported.
if (fnName_.toLowerCase().equals("shutdown")) {
analyzeShutdown(analyzer);
} else {
throw new AnalysisException("Unknown admin function: " + fnName_);
}
}
/**
* Supports optionally specifying the backend and the deadline: either shutdown(),
* shutdown('host:port'), shutdown(deadline), shutdown('host:port', deadline).
*/
private void analyzeShutdown(Analyzer analyzer) throws AnalysisException {
AuthorizationConfig authzConfig = analyzer.getAuthzConfig();
if (authzConfig.isEnabled()) {
// Only admins (i.e. user with ALL privilege on server) can execute admin functions.
String authzServer = authzConfig.getServerName();
Preconditions.checkNotNull(authzServer);
analyzer.registerPrivReq(builder -> builder.onServer(authzServer).all().build());
}
// TODO: this parsing and type checking logic is specific to the command, similar to
// handling of other top-level commands. If we add a lot more of these functions we
// could consider making it generic, similar to handling of normal function calls.
Pair<Expr, Expr> args = getShutdownArgs();
Expr backendExpr = args.first;
Expr deadlineExpr = args.second;
backend_ = null;
deadlineSecs_ = -1;
if (backendExpr != null) {
if (!(backendExpr instanceof StringLiteral)) {
throw new AnalysisException(
"Invalid backend, must be a string literal: " + backendExpr.toSql());
}
backend_ = parseBackendAddress(((StringLiteral) backendExpr).getUnescapedValue());
}
if (deadlineExpr != null) {
deadlineSecs_ = deadlineExpr.evalToNonNegativeInteger(analyzer, "deadline");
}
}
// Return a pair of the backend and deadline arguments, null if not present.
private Pair<Expr, Expr> getShutdownArgs() throws AnalysisException {
if (params_.size() == 0) {
return Pair.create(null, null);
} else if (params_.size() == 1) {
if (params_.get(0).getType().isStringType()) {
return Pair.create(params_.get(0), null);
} else {
return Pair.create(null, params_.get(0));
}
} else if (params_.size() == 2) {
return Pair.create(params_.get(0), params_.get(1));
} else {
throw new AnalysisException("Shutdown takes 0, 1 or 2 arguments: " + toSql());
}
}
// Parse the backend and optional port from 'backend'. Port is set to 0 if not set in
// the string.
private TNetworkAddress parseBackendAddress(String backend) throws AnalysisException {
TNetworkAddress result = new TNetworkAddress();
// Extract host and port from backend string.
String[] toks = backend.trim().split(":");
if (toks.length == 0 || toks.length > 2) {
throw new AnalysisException("Invalid backend address: " + backend);
}
result.hostname = toks[0];
result.port = 0;
if (toks.length == 2) {
try {
result.port = Integer.parseInt(toks[1]);
} catch (NumberFormatException nfe) {
throw new AnalysisException(
"Invalid port number in backend address: " + backend);
}
}
return result;
}
}