/*
Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.fabric.proto.xmlrpc;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mysql.fabric.FabricCommunicationException;
import com.mysql.fabric.Server;
import com.mysql.fabric.ServerGroup;
import com.mysql.fabric.ServerMode;
import com.mysql.fabric.ServerRole;
import com.mysql.fabric.ShardIndex;
import com.mysql.fabric.ShardMapping;
import com.mysql.fabric.ShardMappingFactory;
import com.mysql.fabric.ShardTable;
import com.mysql.fabric.ShardingType;
import com.mysql.fabric.DumpResponse;
import com.mysql.fabric.FabricStateResponse;
import com.mysql.fabric.Response;
/**
* Fabric client using the XML-RPC protocol.
*/
public class XmlRpcClient {
private XmlRpcMethodCaller methodCaller;
public XmlRpcClient(String url, String username, String password) throws FabricCommunicationException {
this.methodCaller = new InternalXmlRpcMethodCaller(url);
if (username != null && !"".equals(username) && password != null) {
this.methodCaller = new AuthenticatedXmlRpcMethodCaller(this.methodCaller,
url, username, password);
}
}
/**
* Unmarshall a response representing a server.
*
* `sharding.lookup_servers' returns ['38dc041b-de86-11e2-a891-e281dccd6dba', '127.0.0.1:3402', True]
* `dump.servers' returns ['38dc041b-de86-11e2-a891-e281dccd6dba', 'shard1', '127.0.0.1', '3402', 3, 3, 1.0]
*/
private static Server unmarshallServer(List serverData) {
Server s = null;
if (serverData.size() <= 4) {
// first format
String hostnameAndPort[] = ((String)serverData.get(1)).split(":");
String hostname = hostnameAndPort[0];
int port = Integer.valueOf(hostnameAndPort[1]);
ServerRole role = (Boolean)serverData.get(2) ? ServerRole.PRIMARY : ServerRole.SECONDARY;
// get(3) = "Running" (optional...) shows up on getGroup(), but not getServersForKey()
s = new Server(null/* group name not known */, (String)serverData.get(0), hostname,
port, ServerMode.READ_WRITE, role, 1.0);
} else if (serverData.size() == 7) {
// second format
String uuid = (String)serverData.get(0);
String groupName = (String)serverData.get(1);
String hostname = (String)serverData.get(2);
int port = Integer.valueOf((String)serverData.get(3));
ServerMode mode = ServerMode.getFromConstant((Integer)serverData.get(4));
ServerRole role = ServerRole.getFromConstant((Integer)serverData.get(5));
double weight = (Double)serverData.get(6);
s = new Server(groupName, uuid, hostname, port, mode, role, weight);
}
return s;
}
/**
* Convert a list of string/string/bool to Server objects.
*/
private static Set<Server> toServerSet(List<List> l) throws FabricCommunicationException {
Set<Server> servers = new HashSet<Server>();
for (List serverData : l) {
Server s = unmarshallServer(serverData);
if (s == null) {
throw new FabricCommunicationException("Unknown format of server object");
}
servers.add(s);
}
return servers;
}
/**
* Call a method and return the result only if the call is successful.
*
* @throws FabricCommunicationException If comm fails or the server reports that the method call failed.
*/
private Object errorSafeCallMethod(String methodName, Object args[]) throws FabricCommunicationException {
List<?> responseData = this.methodCaller.call(methodName, args);
Response response = new Response(responseData);
if (!response.isSuccessful()) {
throw new FabricCommunicationException("Call failed to method `" + methodName + "':\n" + response.getTraceString());
}
return response.getReturnValue();
}
/**
* Call a dump.* method.
*/
private DumpResponse callDumpMethod(String methodName, Object args[])
throws FabricCommunicationException {
List<?> responseData = this.methodCaller.call(methodName, args);
return new DumpResponse(responseData);
}
/**
* Return a list of Fabric servers.
*/
public List<String> getFabricNames() throws FabricCommunicationException {
return callDumpMethod("dump.fabric_nodes", new Object[] {}).getReturnValue();
}
/**
* Return a list of groups present in this fabric.
*/
public Set<String> getGroupNames() throws FabricCommunicationException {
Set<String> groupNames = new HashSet<String>();
for (HashMap<String, String> wrapped : (List<HashMap<String, String>>)errorSafeCallMethod("group.lookup_groups", null)) {
groupNames.add(wrapped.get("group_id"));
}
return groupNames;
}
public ServerGroup getServerGroup(String groupName) throws FabricCommunicationException {
Set<ServerGroup> groups = getServerGroups(groupName).getData();
if (groups.size() == 1) {
return groups.iterator().next();
}
return null;
}
public Set<Server> getServersForKey(String tableName, int key) throws FabricCommunicationException {
return toServerSet((List<List>)errorSafeCallMethod("sharding.lookup_servers", new Object[] {tableName, key}));
}
/**
* Facade for "dump.servers".
*/
public FabricStateResponse<Set<ServerGroup>> getServerGroups(String groupPattern) throws FabricCommunicationException {
int version = 0; // necessary but unused
DumpResponse response = callDumpMethod("dump.servers", new Object[] {version, groupPattern});
// collect all servers by group name
Map<String, Set<Server>> serversByGroupName = new HashMap<String, Set<Server>>();
for (List server : (List<List>) response.getReturnValue()) {
Server s = unmarshallServer(server);
if (serversByGroupName.get(s.getGroupName()) == null) {
serversByGroupName.put(s.getGroupName(), new HashSet<Server>());
}
serversByGroupName.get(s.getGroupName()).add(s);
}
// create group set
Set<ServerGroup> serverGroups = new HashSet<ServerGroup>();
for (Map.Entry<String, Set<Server>> entry : serversByGroupName.entrySet()) {
ServerGroup g = new ServerGroup(entry.getKey(), entry.getValue());
serverGroups.add(g);
}
return new FabricStateResponse<Set<ServerGroup>>(serverGroups, response.getTtl());
}
public FabricStateResponse<Set<ServerGroup>> getServerGroups() throws FabricCommunicationException {
return getServerGroups("");
}
private FabricStateResponse<Set<ShardTable>> getShardTables(String shardMappingId) throws FabricCommunicationException {
int version = 0;
Object args[] = new Object[] {version, shardMappingId};
DumpResponse tablesResponse = callDumpMethod("dump.shard_tables", args);
Set<ShardTable> tables = new HashSet<ShardTable>();
// construct the tables
for (List rawTable : (List<List>) tablesResponse.getReturnValue()) {
String database = (String)rawTable.get(0);
String table = (String)rawTable.get(1);
String column = (String)rawTable.get(2);
String mappingId = (String)rawTable.get(3);
ShardTable st = new ShardTable(database, table, column);
tables.add(st);
}
return new FabricStateResponse<Set<ShardTable>>(tables, tablesResponse.getTtl());
}
private FabricStateResponse<Set<ShardIndex>> getShardIndices(String shardMappingId) throws FabricCommunicationException {
int version = 0;
Object args[] = new Object[] {version, shardMappingId};
DumpResponse indexResponse = callDumpMethod("dump.shard_index", args);
Set<ShardIndex> indices = new HashSet<ShardIndex>();
// construct the index
for (List rawIndexEntry : (List<List>) indexResponse.getReturnValue()) {
String bound = (String)rawIndexEntry.get(0);
String mappingId = (String)rawIndexEntry.get(1);
String shardId = (String)rawIndexEntry.get(2);
String groupName = (String)rawIndexEntry.get(3);
ShardIndex si = new ShardIndex(bound, Integer.valueOf(shardId), groupName);
indices.add(si);
}
return new FabricStateResponse<Set<ShardIndex>>(indices, indexResponse.getTtl());
}
/**
* Retrieve a set of complete shard mappings. The returned mappings include all information
* available about the mapping.
* @param shardMappingIdPattern the shard mapping id to retrieve
*/
public FabricStateResponse<Set<ShardMapping>> getShardMappings(String shardMappingIdPattern) throws FabricCommunicationException {
int version = 0;
Object args[] = new Object[] {version, shardMappingIdPattern}; // common to all calls
DumpResponse mapsResponse = callDumpMethod("dump.shard_maps", args);
// use the lowest ttl of all the calls
long minExpireTimeMillis = System.currentTimeMillis() + (1000 * mapsResponse.getTtl());
// construct the maps
Set<ShardMapping> mappings = new HashSet<ShardMapping>();
for (List rawMapping : (List<List>) mapsResponse.getReturnValue()) {
String mappingId = (String)rawMapping.get(0);
ShardingType shardingType = ShardingType.valueOf((String)rawMapping.get(1));
String globalGroupName = (String)rawMapping.get(2);
FabricStateResponse<Set<ShardTable>> tables = getShardTables(mappingId);
FabricStateResponse<Set<ShardIndex>> indices = getShardIndices(mappingId);
if (tables.getExpireTimeMillis() < minExpireTimeMillis)
minExpireTimeMillis = tables.getExpireTimeMillis();
if (indices.getExpireTimeMillis() < minExpireTimeMillis)
minExpireTimeMillis = indices.getExpireTimeMillis();
ShardMapping m = new ShardMappingFactory().createShardMapping(mappingId, shardingType, globalGroupName,
tables.getData(), indices.getData());
mappings.add(m);
}
return new FabricStateResponse<Set<ShardMapping>>(mappings, minExpireTimeMillis);
}
public FabricStateResponse<Set<ShardMapping>> getShardMappings() throws FabricCommunicationException {
return getShardMappings("");
}
/**
* Create a new HA group.
*/
public void createGroup(String groupName) throws FabricCommunicationException {
errorSafeCallMethod("group.create", new Object[] {groupName});
}
/**
* Create a new server in the given group.
*/
public void createServerInGroup(String groupName, String hostname, int port)
throws FabricCommunicationException {
errorSafeCallMethod("group.add", new Object[] {groupName, hostname + ":" + port});
}
/**
* Create a new shard mapping.
*
* @param type method by which data is distributed to shards
* @param globalGroupName name of global group of the shard mapping
* @returns id of the new shard mapping.
*/
public int createShardMapping(ShardingType type, String globalGroupName) throws FabricCommunicationException {
return (Integer)errorSafeCallMethod("sharding.create_definition", new Object[] {type.toString(), globalGroupName});
}
public void createShardTable(int shardMappingId, String database, String table, String column) throws FabricCommunicationException {
errorSafeCallMethod("sharding.add_table", new Object[] {shardMappingId, database + "." + table, column});
}
public void createShardIndex(int shardMappingId, String groupNameLowerBoundList) throws FabricCommunicationException {
String status = "ENABLED";
errorSafeCallMethod("sharding.add_shard", new Object[] {shardMappingId, groupNameLowerBoundList, status});
}
public void promoteServerInGroup(String groupName, String hostname, int port) throws FabricCommunicationException {
ServerGroup serverGroup = getServerGroup(groupName);
for (Server s : serverGroup.getServers()) {
if (s.getHostname().equals(hostname) &&
s.getPort() == port) {
errorSafeCallMethod("group.promote", new Object[] {groupName, s.getUuid()});
break;
}
}
}
public void reportServerError(Server server, String errorDescription, boolean forceFaulty) throws FabricCommunicationException {
String reporter = "MySQL Connector/J";
String command = "threat.report_error";
if (forceFaulty) {
command = "threat.report_failure";
}
errorSafeCallMethod(command, new Object[] {server.getUuid(), reporter, errorDescription});
}
}