/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.server.distributed.sql;
import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.compression.impl.OZIPCompressionUtil;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;
import com.orientechnologies.orient.core.sql.OCommandExecutorSQLAbstract;
import com.orientechnologies.orient.core.sql.OCommandSQLParsingException;
import com.orientechnologies.orient.core.sql.parser.OHaSyncClusterStatement;
import com.orientechnologies.orient.core.sql.parser.OStatementCache;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;
import com.orientechnologies.orient.core.storage.impl.local.paginated.OPaginatedCluster;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.distributed.*;
import com.orientechnologies.orient.server.distributed.impl.ODistributedAbstractPlugin;
import com.orientechnologies.orient.server.distributed.impl.ODistributedDatabaseChunk;
import com.orientechnologies.orient.server.distributed.impl.task.OCopyDatabaseChunkTask;
import com.orientechnologies.orient.server.distributed.impl.task.OSyncClusterTask;
import com.orientechnologies.orient.server.hazelcast.OHazelcastPlugin;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* SQL HA SYNC CLUSTER command: synchronizes a cluster from distributed servers.
*
* @author Luca Garulli
*
*/
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLHASyncCluster extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
public static final String NAME = "HA SYNC CLUSTER";
private OHaSyncClusterStatement parsedStatement;
public OCommandExecutorSQLHASyncCluster parse(final OCommandRequest iRequest) {
init((OCommandRequestText) iRequest);
try {
parsedStatement = (OHaSyncClusterStatement) OStatementCache.get(this.parserText, getDatabase());
preParsedStatement = parsedStatement;
} catch (OCommandSQLParsingException sqlx) {
throw sqlx;
} catch (Exception e) {
throwParsingException("Error parsing query: \n" + this.parserText + "\n" + e.getMessage(), e);
}
return this;
}
/**
* Execute the SYNC CLUSTER.
*/
public Object execute(final Map<Object, Object> iArgs) {
final ODatabaseDocumentInternal database = getDatabase();
database.checkSecurity(ORule.ResourceGeneric.CLUSTER, "sync", ORole.PERMISSION_UPDATE);
final String dbUrl = database.getURL();
final String path = dbUrl.substring(dbUrl.indexOf(":") + 1);
final OServer serverInstance = OServer.getInstanceByPath(path);
final OHazelcastPlugin dManager = (OHazelcastPlugin) serverInstance.getDistributedManager();
if (dManager == null || !dManager.isEnabled())
throw new OCommandExecutionException("OrientDB is not started in distributed mode");
final String databaseName = database.getName();
try {
if(this.parsedStatement.modeFull) {
return replaceCluster(dManager, database, serverInstance, databaseName, this.parsedStatement.clusterName.getStringValue());
}
// else {
// int merged = 0;
// return String.format("Merged %d records", merged);
// }
} catch (Exception e) {
throw OException.wrapException(new OCommandExecutionException("Cannot execute synchronization of cluster"), e);
}
return "Mode not supported";
}
public static Object replaceCluster(final OHazelcastPlugin dManager, final ODatabaseDocumentInternal database,
final OServer serverInstance, final String databaseName, final String clusterName) throws IOException {
return replaceCluster(dManager, serverInstance, databaseName, clusterName);
}
public static Object replaceCluster(final ODistributedAbstractPlugin dManager, final OServer serverInstance,
final String databaseName, final String clusterName) {
final ODistributedConfiguration cfg = dManager.getDatabaseConfiguration(databaseName);
final String dbPath = serverInstance.getDatabaseDirectory() + databaseName;
final String nodeName = dManager.getLocalNodeName();
final List<String> nodesWhereClusterIsCfg = cfg.getServers(clusterName, null);
nodesWhereClusterIsCfg.remove(nodeName);
if (nodesWhereClusterIsCfg.isEmpty())
throw new OCommandExecutionException(
"Cannot synchronize cluster '" + clusterName + "' because is not configured on any running nodes");
final OSyncClusterTask task = new OSyncClusterTask(clusterName);
final ODistributedResponse response = dManager.sendRequest(databaseName, null, nodesWhereClusterIsCfg, task,
dManager.getNextMessageIdCounter(), ODistributedRequest.EXECUTION_MODE.RESPONSE, null, null);
final Map<String, Object> results = (Map<String, Object>) response.getPayload();
File tempFile = null;
FileOutputStream out = null;
try {
tempFile = new File(Orient.getTempPath() + "/backup_" + databaseName + "_" + clusterName + "_toInstall.zip");
if (tempFile.exists())
tempFile.delete();
else
tempFile.getParentFile().mkdirs();
tempFile.createNewFile();
long fileSize = 0;
out = new FileOutputStream(tempFile, false);
for (Map.Entry<String, Object> r : results.entrySet()) {
final Object value = r.getValue();
if (value instanceof Boolean) {
continue;
} else if (value instanceof Throwable) {
ODistributedServerLog.error(null, nodeName, r.getKey(), ODistributedServerLog.DIRECTION.IN,
"error on installing cluster %s in %s", (Exception) value, databaseName, dbPath);
} else if (value instanceof ODistributedDatabaseChunk) {
ODistributedDatabaseChunk chunk = (ODistributedDatabaseChunk) value;
// DELETE ANY PREVIOUS .COMPLETED FILE
final File completedFile = new File(tempFile.getAbsolutePath() + ".completed");
if (completedFile.exists())
completedFile.delete();
fileSize = writeDatabaseChunk(nodeName, 1, chunk, out);
for (int chunkNum = 2; !chunk.last; chunkNum++) {
final Object result = dManager.sendRequest(databaseName, null, OMultiValue.getSingletonList(r.getKey()),
new OCopyDatabaseChunkTask(chunk.filePath, chunkNum, chunk.offset + chunk.buffer.length, false),
dManager.getNextMessageIdCounter(), ODistributedRequest.EXECUTION_MODE.RESPONSE, null, null);
if (result instanceof Boolean)
continue;
else if (result instanceof Exception) {
ODistributedServerLog.error(null, nodeName, r.getKey(), ODistributedServerLog.DIRECTION.IN,
"error on installing database %s in %s (chunk #%d)", (Exception) result, databaseName, dbPath, chunkNum);
} else if (result instanceof ODistributedDatabaseChunk) {
chunk = (ODistributedDatabaseChunk) result;
fileSize += writeDatabaseChunk(nodeName, chunkNum, chunk, out);
}
}
out.flush();
// CREATE THE .COMPLETED FILE TO SIGNAL EOF
new File(tempFile.getAbsolutePath() + ".completed").createNewFile();
}
}
final String tempDirectoryPath = Orient.getTempPath() + "/backup_" + databaseName + "_" + clusterName + "_toInstall";
final File tempDirectory = new File(tempDirectoryPath);
tempDirectory.mkdirs();
OZIPCompressionUtil.uncompressDirectory(new FileInputStream(tempFile), tempDirectory.getAbsolutePath(), null);
ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.getIfDefined();
final boolean openDatabaseHere = db == null;
if (db == null)
db = serverInstance.openDatabase("plocal:" + dbPath, "", "", null, true);
try {
final OAbstractPaginatedStorage stg = (OAbstractPaginatedStorage) db.getStorage().getUnderlying();
// TODO: FREEZE COULD IT NOT NEEDED
stg.freeze(false);
try {
final OPaginatedCluster cluster = (OPaginatedCluster) stg.getClusterByName(clusterName);
final File tempClusterFile = new File(tempDirectoryPath + "/" + clusterName + OPaginatedCluster.DEF_EXTENSION);
cluster.replaceFile(tempClusterFile);
} finally {
stg.release();
}
db.getLocalCache().invalidate();
} finally {
if (openDatabaseHere)
db.close();
}
return String.format("Cluster correctly replaced, transferred %d bytes", fileSize);
} catch (Exception e) {
ODistributedServerLog.error(null, nodeName, null, ODistributedServerLog.DIRECTION.NONE,
"error on transferring database '%s' to '%s'", e, databaseName, tempFile);
throw OException.wrapException(new ODistributedException("Error on transferring database"), e);
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException e) {
}
}
}
@Override
public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
return DISTRIBUTED_EXECUTION_MODE.LOCAL;
}
@Override
public long getDistributedTimeout() {
return OGlobalConfiguration.DISTRIBUTED_DEPLOYDB_TASK_SYNCH_TIMEOUT.getValueAsLong();
}
@Override
public QUORUM_TYPE getQuorumType() {
return QUORUM_TYPE.ALL;
}
@Override
public String getSyntax() {
return "HA SYNC CLUSTER <cluster-name> [-full_replace|-merge]";
}
protected static long writeDatabaseChunk(final String iNodeName, final int iChunkId, final ODistributedDatabaseChunk chunk,
final FileOutputStream out) throws IOException {
ODistributedServerLog.warn(null, iNodeName, null, ODistributedServerLog.DIRECTION.NONE, "- writing chunk #%d offset=%d size=%s",
iChunkId, chunk.offset, OFileUtils.getSizeAsString(chunk.buffer.length));
out.write(chunk.buffer);
return chunk.buffer.length;
}
}