blob: 17e0ecbb64ce781cb6246d690471ac6fc7825ef2 [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.ranger.services.kylin.client;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.log4j.Logger;
import org.apache.ranger.plugin.client.BaseClient;
import org.apache.ranger.plugin.client.HadoopException;
import org.apache.ranger.plugin.util.PasswordUtils;
import org.apache.ranger.services.kylin.client.json.model.KylinProjectResponse;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
public class KylinClient extends BaseClient {
private static final Logger LOG = Logger.getLogger(KylinClient.class);
private static final String EXPECTED_MIME_TYPE = "application/json";
private static final String KYLIN_LIST_API_ENDPOINT = "/kylin/api/projects";
private static final String ERROR_MESSAGE = " You can still save the repository and start creating "
+ "policies, but you would not be able to use autocomplete for "
+ "resource names. Check ranger_admin.log for more info.";
private String kylinUrl;
private String userName;
private String password;
public KylinClient(String serviceName, Map<String, String> configs) {
super(serviceName, configs, "kylin-client");
this.kylinUrl = configs.get("kylin.url");
this.userName = configs.get("username");
this.password = configs.get("password");
if (StringUtils.isEmpty(this.kylinUrl)) {
LOG.error("No value found for configuration 'kylin.url'. Kylin resource lookup will fail.");
}
if (StringUtils.isEmpty(this.userName)) {
LOG.error("No value found for configuration 'username'. Kylin resource lookup will fail.");
}
if (StringUtils.isEmpty(this.password)) {
LOG.error("No value found for configuration 'password'. Kylin resource lookup will fail.");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Kylin client is build with url [" + this.kylinUrl + "], user: [" + this.userName
+ "], password: [" + "*********" + "].");
}
}
public List<String> getProjectList(final String projectMatching, final List<String> existingProjects) {
if (LOG.isDebugEnabled()) {
LOG.debug("Getting kylin project list for projectMatching: " + projectMatching + ", existingProjects: "
+ existingProjects);
}
Subject subj = getLoginSubject();
if (subj == null) {
return Collections.emptyList();
}
List<String> ret = Subject.doAs(subj, new PrivilegedAction<List<String>>() {
@Override
public List<String> run() {
ClientResponse response = getClientResponse(kylinUrl, userName, password);
List<KylinProjectResponse> projectResponses = getKylinProjectResponse(response);
if (CollectionUtils.isEmpty(projectResponses)) {
return Collections.emptyList();
}
List<String> projects = getProjectFromResponse(projectMatching, existingProjects, projectResponses);
return projects;
}
});
if (LOG.isDebugEnabled()) {
LOG.debug("Getting kylin project list result: " + ret);
}
return ret;
}
private static ClientResponse getClientResponse(String kylinUrl, String userName, String password) {
ClientResponse response = null;
String[] kylinUrls = kylinUrl.trim().split("[,;]");
if (ArrayUtils.isEmpty(kylinUrls)) {
return null;
}
Client client = Client.create();
String decryptedPwd = PasswordUtils.getDecryptPassword(password);
client.addFilter(new HTTPBasicAuthFilter(userName, decryptedPwd));
for (String currentUrl : kylinUrls) {
if (StringUtils.isBlank(currentUrl)) {
continue;
}
String url = currentUrl.trim() + KYLIN_LIST_API_ENDPOINT;
try {
response = getProjectResponse(url, client);
if (response != null) {
if (response.getStatus() == HttpStatus.SC_OK) {
break;
} else {
response.close();
}
}
} catch (Throwable t) {
String msgDesc = "Exception while getting kylin response, kylinUrl: " + url;
LOG.error(msgDesc, t);
}
}
client.destroy();
return response;
}
private List<KylinProjectResponse> getKylinProjectResponse(ClientResponse response) {
List<KylinProjectResponse> projectResponses = null;
try {
if (response != null) {
if (response.getStatus() == HttpStatus.SC_OK) {
String jsonString = response.getEntity(String.class);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
projectResponses = gson.fromJson(jsonString, new TypeToken<List<KylinProjectResponse>>() {
}.getType());
} else {
String msgDesc = "Unable to get a valid response for " + "expected mime type : [" + EXPECTED_MIME_TYPE
+ "], kylinUrl: " + kylinUrl + " - got http response code " + response.getStatus();
LOG.error(msgDesc);
HadoopException hdpException = new HadoopException(msgDesc);
hdpException.generateResponseDataMap(false, msgDesc, msgDesc + ERROR_MESSAGE, null, null);
throw hdpException;
}
} else {
String msgDesc = "Unable to get a valid response for " + "expected mime type : [" + EXPECTED_MIME_TYPE
+ "], kylinUrl: " + kylinUrl + " - got null response.";
LOG.error(msgDesc);
HadoopException hdpException = new HadoopException(msgDesc);
hdpException.generateResponseDataMap(false, msgDesc, msgDesc + ERROR_MESSAGE, null, null);
throw hdpException;
}
} catch (HadoopException he) {
throw he;
} catch (Throwable t) {
String msgDesc = "Exception while getting kylin project response, kylinUrl: " + kylinUrl;
HadoopException hdpException = new HadoopException(msgDesc, t);
LOG.error(msgDesc, t);
hdpException.generateResponseDataMap(false, BaseClient.getMessage(t), msgDesc + ERROR_MESSAGE, null, null);
throw hdpException;
} finally {
if (response != null) {
response.close();
}
}
return projectResponses;
}
private static ClientResponse getProjectResponse(String url, Client client) {
if (LOG.isDebugEnabled()) {
LOG.debug("getProjectResponse():calling " + url);
}
WebResource webResource = client.resource(url);
ClientResponse response = webResource.accept(EXPECTED_MIME_TYPE).get(ClientResponse.class);
if (response != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("getProjectResponse():response.getStatus()= " + response.getStatus());
}
if (response.getStatus() != HttpStatus.SC_OK) {
LOG.warn("getProjectResponse():response.getStatus()= " + response.getStatus() + " for URL " + url
+ ", failed to get kylin project list.");
String jsonString = response.getEntity(String.class);
LOG.warn(jsonString);
}
}
return response;
}
private static List<String> getProjectFromResponse(String projectMatching, List<String> existingProjects,
List<KylinProjectResponse> projectResponses) {
List<String> projcetNames = new ArrayList<String>();
for (KylinProjectResponse project : projectResponses) {
String projectName = project.getName();
if (CollectionUtils.isNotEmpty(existingProjects) && existingProjects.contains(projectName)) {
continue;
}
if (StringUtils.isEmpty(projectMatching) || projectMatching.startsWith("*")
|| projectName.toLowerCase().startsWith(projectMatching.toLowerCase())) {
if (LOG.isDebugEnabled()) {
LOG.debug("getProjectFromResponse(): Adding kylin project " + projectName);
}
projcetNames.add(projectName);
}
}
return projcetNames;
}
public static Map<String, Object> connectionTest(String serviceName, Map<String, String> configs) {
KylinClient kylinClient = getKylinClient(serviceName, configs);
List<String> strList = kylinClient.getProjectList(null, null);
boolean connectivityStatus = false;
if (CollectionUtils.isNotEmpty(strList)) {
if (LOG.isDebugEnabled()) {
LOG.debug("ConnectionTest list size" + strList.size() + " kylin projects");
}
connectivityStatus = true;
}
Map<String, Object> responseData = new HashMap<String, Object>();
if (connectivityStatus) {
String successMsg = "ConnectionTest Successful";
BaseClient.generateResponseDataMap(connectivityStatus, successMsg, successMsg, null, null, responseData);
} else {
String failureMsg = "Unable to retrieve any kylin projects using given parameters.";
BaseClient.generateResponseDataMap(connectivityStatus, failureMsg, failureMsg + ERROR_MESSAGE, null, null,
responseData);
}
return responseData;
}
public static KylinClient getKylinClient(String serviceName, Map<String, String> configs) {
KylinClient kylinClient = null;
if (LOG.isDebugEnabled()) {
LOG.debug("Getting KylinClient for datasource: " + serviceName);
}
if (MapUtils.isEmpty(configs)) {
String msgDesc = "Could not connect kylin as connection configMap is empty.";
LOG.error(msgDesc);
HadoopException hdpException = new HadoopException(msgDesc);
hdpException.generateResponseDataMap(false, msgDesc, msgDesc + ERROR_MESSAGE, null, null);
throw hdpException;
} else {
kylinClient = new KylinClient(serviceName, configs);
}
return kylinClient;
}
}