| /* |
| * 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.server.tomcat; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.logging.Logger; |
| import org.apache.hadoop.security.SecureClientLogin; |
| import org.apache.http.HttpEntity; |
| import org.apache.http.client.methods.HttpPost; |
| import org.apache.http.entity.ByteArrayEntity; |
| import org.apache.http.util.EntityUtils; |
| import org.apache.ranger.authorization.utils.StringUtil; |
| import org.apache.solr.client.solrj.SolrClient; |
| import org.apache.solr.client.solrj.SolrServerException; |
| import org.apache.solr.client.solrj.impl.CloudSolrClient; |
| import org.apache.solr.client.solrj.impl.HttpClientUtil; |
| import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder; |
| import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder; |
| import org.apache.solr.client.solrj.request.CollectionAdminRequest; |
| import org.apache.solr.client.solrj.response.CollectionAdminResponse; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.cloud.SolrZkClient; |
| import org.apache.solr.common.cloud.SolrZooKeeper; |
| import org.apache.solr.common.cloud.ZkConfigManager; |
| import org.noggit.JSONParser; |
| import org.noggit.ObjectBuilder; |
| |
| import com.google.protobuf.TextFormat.ParseException; |
| |
| public class SolrCollectionBootstrapper extends Thread { |
| |
| private static final Logger logger = Logger |
| .getLogger(SolrCollectionBootstrapper.class.getName()); |
| final static String SOLR_ZK_HOSTS = "ranger.audit.solr.zookeepers"; |
| final static String SOLR_COLLECTION_NAME = "ranger.audit.solr.collection.name"; |
| final static String SOLR_CONFIG_NAME = "ranger.audit.solr.config.name"; |
| final static String SOLR_NO_SHARDS = "ranger.audit.solr.no.shards"; |
| final static String SOLR_MAX_SHARD_PER_NODE = "ranger.audit.solr.max.shards.per.node"; |
| final static String SOLR_NO_REPLICA = "ranger.audit.solr.no.replica"; |
| final static String SOLR_TIME_INTERVAL = "ranger.audit.solr.time.interval"; |
| final static String SOLR_BOOTSTRP_MAX_RETRY = "ranger.audit.solr.max.retry"; |
| final static String PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG = "java.security.auth.login.config"; |
| public static final String DEFAULT_COLLECTION_NAME = "ranger_audits"; |
| public static final String DEFAULT_CONFIG_NAME = "ranger_audits"; |
| public static final long DEFAULT_SOLR_TIME_INTERVAL_MS = 60000L; |
| private static final int TRY_UNTIL_SUCCESS = -1; |
| public static final int DEFAULT_SOLR_BOOTSTRP_MAX_RETRY = TRY_UNTIL_SUCCESS; |
| private static final String AUTH_TYPE_KERBEROS = "kerberos"; |
| private static final String AUTHENTICATION_TYPE = "hadoop.security.authentication"; |
| private static final String RANGER_SERVICE_HOSTNAME = "ranger.service.host"; |
| private static final String ADMIN_USER_PRINCIPAL = "ranger.admin.kerberos.principal"; |
| private static final String SSL_ENABLED_PARAM = "ranger.service.https.attrib.ssl.enabled"; |
| private File configSetFolder = null; |
| |
| boolean solr_cloud_mode = false; |
| boolean is_completed = false; |
| boolean isKERBEROS = false; |
| private boolean isSSLEnabled = false; |
| String principal = null; |
| String hostName; |
| String keytab; |
| String nameRules; |
| String solr_collection_name; |
| String solr_config_name; |
| Path path_for_cloud_mode; |
| int no_of_replicas; |
| int no_of_shards; |
| int max_node_per_shards; |
| int max_retry; |
| int retry_counter = 0; |
| Long time_interval; |
| SolrClient solrClient = null; |
| CloudSolrClient solrCloudClient = null; |
| SolrZooKeeper solrZookeeper = null; |
| SolrZkClient zkClient = null; |
| |
| public SolrCollectionBootstrapper() throws IOException { |
| logger.info("Starting Solr Setup"); |
| |
| logger.info("AUTHENTICATION_TYPE : " + EmbeddedServerUtil.getConfig(AUTHENTICATION_TYPE)); |
| if (EmbeddedServerUtil.getConfig(AUTHENTICATION_TYPE) != null |
| && EmbeddedServerUtil.getConfig(AUTHENTICATION_TYPE).trim().equalsIgnoreCase( |
| AUTH_TYPE_KERBEROS)) { |
| isKERBEROS = true; |
| hostName = EmbeddedServerUtil.getConfig(RANGER_SERVICE_HOSTNAME); |
| try { |
| principal = SecureClientLogin.getPrincipal( |
| EmbeddedServerUtil.getConfig(ADMIN_USER_PRINCIPAL), hostName); |
| } catch (IOException ignored) { |
| logger.warning("Failed to get ranger.admin.kerberos.principal. Reason: " |
| + ignored.toString()); |
| } |
| } |
| |
| solr_collection_name = EmbeddedServerUtil.getConfig(SOLR_COLLECTION_NAME, |
| DEFAULT_COLLECTION_NAME); |
| logger.info("Solr Collection name provided is : " |
| + solr_collection_name); |
| solr_config_name = EmbeddedServerUtil.getConfig(SOLR_CONFIG_NAME, DEFAULT_CONFIG_NAME); |
| logger.info("Solr Config name provided is : " + solr_config_name); |
| no_of_replicas = EmbeddedServerUtil.getIntConfig(SOLR_NO_REPLICA, 1); |
| logger.info("No. of replicas provided is : " + no_of_replicas); |
| |
| no_of_shards = EmbeddedServerUtil.getIntConfig(SOLR_NO_SHARDS, 1); |
| logger.info("No. of shards provided is : " + no_of_shards); |
| max_node_per_shards = EmbeddedServerUtil.getIntConfig(SOLR_MAX_SHARD_PER_NODE, 1); |
| logger.info("Max no of nodes per shards provided is : " |
| + max_node_per_shards); |
| |
| time_interval = EmbeddedServerUtil.getLongConfig(SOLR_TIME_INTERVAL, |
| DEFAULT_SOLR_TIME_INTERVAL_MS); |
| logger.info("Solr time interval provided is : " + time_interval); |
| |
| max_retry = EmbeddedServerUtil.getIntConfig(SOLR_BOOTSTRP_MAX_RETRY, DEFAULT_SOLR_BOOTSTRP_MAX_RETRY); |
| if (System.getProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG) == null) { |
| System.setProperty(PROP_JAVA_SECURITY_AUTH_LOGIN_CONFIG, |
| "/dev/null"); |
| } |
| |
| String basedir = new File(".").getCanonicalPath(); |
| String solrFileDir = new File(basedir).getParent(); |
| |
| path_for_cloud_mode = Paths.get(solrFileDir, "contrib", |
| "solr_for_audit_setup", "conf"); |
| configSetFolder = path_for_cloud_mode.toFile(); |
| String sslEnabledProp = EmbeddedServerUtil.getConfig(SSL_ENABLED_PARAM); |
| isSSLEnabled = ("true".equalsIgnoreCase(sslEnabledProp)); |
| } |
| |
| public void run() { |
| logger.info("Started run method"); |
| |
| String zkHosts = ""; |
| List<String> zookeeperHosts = null; |
| if (EmbeddedServerUtil.getConfig(SOLR_ZK_HOSTS) != null |
| && !StringUtil.isEmpty(EmbeddedServerUtil.getConfig(SOLR_ZK_HOSTS))) { |
| zkHosts = EmbeddedServerUtil.getConfig(SOLR_ZK_HOSTS).trim(); |
| zookeeperHosts = new ArrayList<String>(Arrays.asList(zkHosts |
| .split(","))); |
| } |
| if (zookeeperHosts != null |
| && !zookeeperHosts.isEmpty() |
| && zookeeperHosts.stream().noneMatch( |
| h -> h.equalsIgnoreCase("none"))) { |
| logger.info("Solr zkHosts=" + zkHosts + ", collectionName=" |
| + solr_collection_name); |
| while (!is_completed && (max_retry == TRY_UNTIL_SUCCESS || retry_counter < max_retry)) { |
| try { |
| if (connect(zookeeperHosts)) { |
| if (solr_cloud_mode) { |
| if (uploadConfiguration() && createCollection() |
| ) { |
| is_completed = true; |
| break; |
| } else { |
| logErrorMessageAndWait( |
| "Error while performing operations on solr. ", |
| null); |
| } |
| } |
| |
| } else { |
| logErrorMessageAndWait( |
| "Cannot connect to solr kindly check the solr related configs. ", |
| null); |
| } |
| } catch (Exception ex) { |
| logErrorMessageAndWait("Error while configuring solr. ", ex); |
| } |
| |
| } |
| |
| } else { |
| logger.severe("Solr ZKHosts for Audit are empty. Please set property " |
| + SOLR_ZK_HOSTS); |
| } |
| |
| } |
| |
| private boolean connect(List<String> zookeeperHosts) { |
| try { |
| logger.info("Solr is in Cloud mode"); |
| if (isKERBEROS) { |
| setHttpClientBuilderForKrb(); |
| } |
| solrCloudClient = new CloudSolrClient.Builder(zookeeperHosts, |
| Optional.empty()).build(); |
| solrCloudClient.setDefaultCollection(solr_collection_name); |
| solrClient = solrCloudClient; |
| solr_cloud_mode = true; |
| |
| return true; |
| } catch (Exception ex) { |
| logger.severe("Can't connect to Solr server. ZooKeepers=" |
| + zookeeperHosts + ", collection=" + solr_collection_name |
| + ex); |
| return false; |
| } |
| } |
| |
| private void setHttpClientBuilderForKrb() { |
| Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder(); |
| SolrHttpClientBuilder kb = krbBuild.getBuilder(); |
| HttpClientUtil.setHttpClientBuilder(kb); |
| } |
| |
| public static Map postDataAndGetResponse(CloudSolrClient cloudClient, |
| String uri, ByteBuffer bytarr) throws IOException { |
| HttpPost httpPost = null; |
| HttpEntity entity; |
| String response = null; |
| Map m = null; |
| try { |
| httpPost = new HttpPost(uri); |
| |
| httpPost.setHeader("Content-Type", "application/octet-stream"); |
| |
| httpPost.setEntity(new ByteArrayEntity(bytarr.array(), bytarr |
| .arrayOffset(), bytarr.limit())); |
| entity = cloudClient.getLbClient().getHttpClient().execute(httpPost) |
| .getEntity(); |
| try { |
| response = EntityUtils.toString(entity, StandardCharsets.UTF_8); |
| m = (Map) ObjectBuilder.getVal(new JSONParser( |
| new StringReader(response))); |
| } catch (JSONParser.ParseException e) { |
| logger.severe("Error response: " + response); |
| throw new AssertionError(e); |
| } |
| } finally { |
| httpPost.releaseConnection(); |
| } |
| return m; |
| } |
| private boolean uploadConfiguration() { |
| try { |
| solrCloudClient.connect(); |
| zkClient = solrCloudClient.getZkStateReader().getZkClient(); |
| |
| if (zkClient != null) { |
| ZkConfigManager zkConfigManager = new ZkConfigManager(zkClient); |
| |
| boolean configExists = zkConfigManager.configExists(solr_config_name); |
| if (!configExists) { |
| logger.info("Config does not exist with name " + solr_config_name); |
| String zipOfConfigs = null; |
| String[] files = configSetFolder.list(); |
| for (String file : files) { |
| if(file != null) { |
| if (file.equals("solr_audit_conf.zip")) { |
| zipOfConfigs = file; |
| break; |
| } |
| } |
| } |
| if(zipOfConfigs == null) { |
| throw new FileNotFoundException("Could Not Find Configs Zip File : "+ getConfigSetFolder()); |
| } |
| File file = new File(configSetFolder + "/" + zipOfConfigs); |
| byte[] arrByte = Files.readAllBytes(file.toPath()); |
| ByteBuffer byteBuffer = ByteBuffer.wrap(arrByte); |
| Set<String> nodes = solrCloudClient.getClusterStateProvider().getLiveNodes(); |
| String baseUrl = null; |
| String[] nodeArr = nodes.toArray(new String[0]); |
| /*getting nodes URL as 'solr_8983', so converting it to 'solr/9893'*/ |
| baseUrl = nodeArr[0].replaceAll("_", "/"); |
| String protocol = isSSLEnabled ? "https": "http"; |
| String uploadConfigsUrl = String.format("%s://%s/admin/configs?action=UPLOAD&name=%s", protocol, baseUrl.toString(),solr_config_name); |
| postDataAndGetResponse(solrCloudClient,uploadConfigsUrl ,byteBuffer); |
| return true; |
| } |
| else { |
| logger.info("Config already exists with name " + solr_config_name ); |
| return true; |
| } |
| } else { |
| logger.severe("Solr is in cloud mode and could not find the zookeeper client for performing upload operations. "); |
| return false; |
| } |
| } catch (Exception ex) { |
| logger.severe("Error while uploading configuration : " + ex); |
| return false; |
| } |
| } |
| |
| private void logErrorMessageAndWait(String msg, Exception exception) { |
| retry_counter++; |
| String attempMessage; |
| if (max_retry != TRY_UNTIL_SUCCESS) { |
| attempMessage = (retry_counter == max_retry) ? ("Maximum attempts reached for setting up Solr.") |
| : ("[retrying after " + time_interval + " ms]. No. of attempts left : " |
| + (max_retry - retry_counter) + " . Maximum attempts : " + max_retry); |
| } else { |
| attempMessage = "[retrying after " + time_interval + " ms]"; |
| } |
| StringBuilder errorBuilder = new StringBuilder(); |
| errorBuilder.append(msg); |
| if (exception != null) { |
| errorBuilder.append("Error : ".concat(exception.getMessage() + ". ")); |
| } |
| errorBuilder.append(attempMessage); |
| logger.severe(errorBuilder.toString()); |
| try { |
| Thread.sleep(time_interval); |
| } catch (InterruptedException ex) { |
| logger.info("sleep interrupted: " + ex.getMessage()); |
| } |
| } |
| |
| private boolean createCollection() { |
| try { |
| List<String> allCollectionList = getCollections(); |
| if (allCollectionList != null) { |
| if (!allCollectionList.contains(solr_collection_name)) { |
| |
| CollectionAdminRequest.Create createCollection = CollectionAdminRequest |
| .createCollection(solr_collection_name, |
| solr_config_name, no_of_shards, |
| no_of_replicas); |
| createCollection.setMaxShardsPerNode(max_node_per_shards); |
| CollectionAdminResponse createResponse = createCollection |
| .process(solrClient); |
| if (createResponse.getStatus() != 0) { |
| logger.severe("Error creating collection. collectionName=" |
| + solr_collection_name |
| + " , solr config name = " |
| + solr_config_name |
| + " , replicas = " |
| + no_of_replicas |
| + ", shards=" |
| + no_of_shards |
| + " , max node per shards = " |
| + max_node_per_shards |
| + ", response=" |
| + createResponse); |
| return false; |
| } else { |
| logger.info("Created collection " |
| + solr_collection_name + " with config name " |
| + solr_config_name + " replicas = " |
| + no_of_replicas + " Shards = " + no_of_shards |
| + " max node per shards = " |
| + max_node_per_shards); |
| return true; |
| } |
| } else { |
| logger.info("Collection already exists with name " |
| + solr_collection_name); |
| return true; |
| } |
| } else { |
| logger.severe("Error while connecting to solr "); |
| return false; |
| } |
| } catch (Exception ex) { |
| logger.severe("Error while creating collection in solr : " + ex); |
| return false; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private List<String> getCollections() throws IOException, ParseException { |
| try { |
| CollectionAdminRequest.List colListReq = new CollectionAdminRequest.List(); |
| CollectionAdminResponse response = colListReq.process(solrClient); |
| if (response.getStatus() != 0) { |
| logger.severe("Error getting collection list from solr. response=" |
| + response); |
| return null; |
| } |
| return (List<String>) response.getResponse().get("collections"); |
| } catch (SolrException e) { |
| logger.severe("getCollections() operation failed : " + e); |
| return null; |
| } catch (SolrServerException e) { |
| logger.severe("getCollections() operation failed : " + e); |
| return null; |
| } |
| |
| } |
| |
| private File getConfigSetFolder() { |
| return configSetFolder; |
| } |
| |
| } |