/*
*
* Copyright (c) 2014 CA. All rights reserved.
*
* 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.
* IN NO EVENT WILL CA BE LIABLE TO THE END USER OR ANY THIRD PARTY FOR ANY LOSS
* OR DAMAGE, DIRECT OR INDIRECT, FROM THE USE OF THIS MATERIAL,
* INCLUDING WITHOUT LIMITATION, LOST PROFITS, BUSINESS INTERRUPTION, GOODWILL,
* OR LOST DATA, EVEN IF CA IS EXPRESSLY ADVISED OF SUCH LOSS OR DAMAGE.
*
*/
package com.ca.apm.mongo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.bson.BasicBSONObject;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
public final class ShardCluster extends Topology {
public ShardCluster(
final Properties props,
final String h,
final int p,
final Logger l
) {
super(props, h, p, l, ClusterType.SHARDED_CLUSTER);
}
private String host = dbHost;
private int port = dbPort;
public void discoverServers(final String nodeType) throws Exception {
List<String> shardRouters = null;
List<String> shardServers = null;
List<String> cfgServers = null;
logger.log(Level.FINE, "Shard Cluster Node Type: {0}", nodeType);
if ("shardRouter".equals(nodeType)) {
shardServers = getShardsFromMongos(host, port);
shardRouters = getMongosFromConfig(host, port);
cfgServers = getConfigServersFromMongos(host, port);
} else if ("shardConfigServer".equals(nodeType)) {
shardRouters = getMongosFromConfig(host, port);
for (String shardRouter : shardRouters) {
MongoServer ms = new MongoServer(shardRouter);
cfgServers = getConfigServersFromMongos(
ms.getHost(), ms.getPort());
break;
}
shardServers = getShardsFromConfig(host, port);
} else if ("shardMember".equals(nodeType)) {
cfgServers = getConfigServersFromShard(host, port);
for (String cfgServer : cfgServers) {
MongoServer ms = new MongoServer(cfgServer);
shardRouters =
getMongosFromConfig(ms.getHost(), ms.getPort());
break;
}
for (String cfgServer : cfgServers) {
MongoServer ms = new MongoServer(cfgServer);
shardServers =
getShardsFromConfig(ms.getHost(), ms.getPort());
break;
}
} else {
throw new RuntimeException("UnKnown Cluster node type!");
}
members.addAll(shardRouters);
members.addAll(shardServers);
members.addAll(cfgServers);
}
private List<String> getShardsFromMongos(
final String host,
final int port
) throws Exception {
final List<String> shardResult = new ArrayList<String>();
final CommandResult cr = dbAdminCmd(host, port, "listShards");
if (cr.ok()) {
final BasicDBList shardList= (BasicDBList)cr.get("shards");
for (Object obj : shardList) {
final BasicDBObject bdbo = (BasicDBObject) obj;
String shards = bdbo.getString("host");
if (shards.indexOf("/") != -1) {
final String [] shardMembers =
shards.split("/")[1].split(",");
for (String member : shardMembers) {
final MongoServer ms =
new MongoServer(member);
shardResult.addAll(discoverReplicas(
ms.getHost(), ms.getPort()));
}
} else {
// single node shard
shardResult.add(shards);
}
}
}
return shardResult;
}
private List<String> getShardsFromConfig(
final String host,
final int port
) {
final List<String> shardList = new ArrayList<String>();
MongoClient dbClient = null;
try {
dbClient = setupDbClient(host, port);
final DB configDB = dbClient.getDB("config");
final DBCursor shardsCursor =
configDB.getCollectionFromString("shards").find();
while (shardsCursor.hasNext()) {
final DBObject dbo = shardsCursor.next();
String shards = (String) dbo.get("host");
String [] shardMembers = getShardMembers(shards);
for (String member : shardMembers) {
shardList.add(member);
}
}
} catch (Exception e) {
logger.log(Level.WARNING,
"Exception getting shards from cfg servers: {0}", e);
} finally {
if (dbClient != null) {
dbClient.close();
}
}
return shardList;
}
private List<String> getMongosFromConfig(
final String host,
final int port
) {
final List<String> shardRouters = new ArrayList<String>();
MongoClient dbClient = null;
try {
dbClient = setupDbClient(host, port);
final DB configDB = dbClient.getDB("config");
final DBCursor mongosCursor =
configDB.getCollectionFromString("mongos").find();
while (mongosCursor.hasNext()) {
final DBObject dbo = mongosCursor.next();
final String mongos = (String) dbo.get("_id");
shardRouters.add(mongos);
}
} catch (Exception e) {
logger.log(Level.WARNING,
"Exception getting mongos from cfg server(s): {0}", e);
} finally {
if (dbClient != null) {
dbClient.close();
}
}
return shardRouters;
}
private List<String> getConfigServersFromMongos(
final String host,
final int port
) throws Exception {
final List<String> cfgServers = new ArrayList<String>();
final BasicBSONObject parsed = getParsedCmdLineOpts(host, port);
final BasicBSONObject sharding =
(BasicBSONObject) parsed.get("sharding");
String cfgServerString;
if (sharding != null)
cfgServerString = sharding.getString("configDB");
else
cfgServerString = (String) parsed.get("configDB");
addCommaSeparatedHosts(cfgServers, cfgServerString);
return cfgServers;
}
private List<String> getConfigServersFromShard(
final String host,
final int port
) throws Exception {
final List<String> cfgServers = new ArrayList<String>();
String cHost = host;
int cPort = port;
final CommandResult isMaster = dbAdminCmd(cHost, cPort, "isMaster");
// we can't run the DB command "shardingState" from a node
// which isn't a master. This should only apply to non-primary
// replica members, so find the primary and run the command on it.
if (! isMaster.getBoolean("ismaster")) {
if (isMaster.containsField("primary")) {
final String primary = isMaster.getString("primary");
final MongoServer ms = new MongoServer(primary);
cHost = ms.getHost();
cPort = ms.getPort();
}
}
CommandResult shardState =
dbAdminCmd(cHost, cPort, "shardingState");
if (shardState.ok()) {
final String cfgSrvs =
shardState.getString("configServer");
addCommaSeparatedHosts(cfgServers, cfgSrvs);
}
return cfgServers;
}
private String[] getShardMembers(final String shardString) {
String shards = shardString;
if (shards.indexOf("/") != -1) {
shards = shards.split("/")[1];
}
return shards.split(",");
}
private void addCommaSeparatedHosts(
final Collection<String> c,
final String hosts) {
if (hosts != null) {
for (String host : hosts.split(",")) {
c.add(host);
}
}
}
private BasicBSONObject getParsedCmdLineOpts(
final String host,
final int port
) throws Exception {
BasicBSONObject parsed = null;
final CommandResult clOptions =
dbAdminCmd(host, port, "getCmdLineOpts");
if (clOptions.ok()) {
parsed = (BasicBSONObject) clOptions.get("parsed");
} else {
throw new RuntimeException(
"Failed to get command line options!");
}
return parsed;
}
}