/*
* Copyright 2015 OrientDB LTD (info--at--orientdb.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.
*/
package com.orientechnologies.orient.server.distributed.scenariotest;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.server.distributed.OModifiableDistributedConfiguration;
import com.orientechnologies.orient.server.distributed.impl.ODistributedStorage;
import com.orientechnologies.orient.server.hazelcast.OHazelcastPlugin;
import org.junit.Ignore;
import org.junit.Test;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static org.junit.Assert.assertEquals;
/**
* Checks for consistency on the cluster with these steps: - 2 server (quorum=2) - a record is inserted on server1 - the record
* (version 1) is present in full replica on all the servers - a delay is inserted into tx of server0 - client c0 begins a tx on
* server1 and client c1 begins a tx on server2 at the same time (both txs for updating the same record) - client c0 waits the delay
* - client c1 tries to update the record, but server0 return a lockedrecord exception - after c1 gives up, c0 tries the tx and
* succeeds
*/
public class TwoClientsRecordUpdateTxOnTwoServersWithQuorum2ScenarioTest extends AbstractScenarioTest {
private final String RECORD_ID = "R001";
private HashMap<String, Object> lukeFields = new HashMap<String, Object>() {
{
put("firstName", "Luke");
put("lastName", "Skywalker");
}
};
private HashMap<String, Object> darthFields = new HashMap<String, Object>() {
{
put("firstName", "Darth");
put("lastName", "Vader");
}
};
@Test
@Ignore
public void test() throws Exception {
maxRetries = 10;
init(2);
prepare(false);
execute();
}
@Override
public void executeTest() throws Exception {
setWriteQuorum(2);
ODatabaseDocumentTx dbServer0 = poolFactory.get(getDatabaseURL(serverInstance.get(0)), "admin", "admin").acquire();
// inserts record
ODatabaseRecordThreadLocal.INSTANCE.set(dbServer0);
ODocument recordServer0 = new ODocument("Person").fields("id", RECORD_ID, "firstName", "Han", "lastName", "Solo");
recordServer0.save();
// waits for propagation of the record on all the servers
waitForInsertedRecordPropagation(RECORD_ID);
// retrieves record from server1 and checks they're equal
ODocument recordServer1 = retrieveRecord(getDatabaseURL(serverInstance.get(1)), RECORD_ID);
assertEquals(recordServer1.getVersion(), recordServer0.getVersion());
assertEquals(recordServer1.field("id"), recordServer0.field("id"));
assertEquals(recordServer1.field("firstName"), recordServer0.field("firstName"));
assertEquals(recordServer1.field("lastName"), recordServer0.field("lastName"));
// gets the actual version of record from server0
int actualVersion = recordServer0.getVersion();
// sets a delay for operations on distributed storage of server0
((ODistributedStorage) dbServer0.getStorage())
.setEventListener(new AfterRecordLockDelayer("server0", DOCUMENT_WRITE_TIMEOUT / 4));
// updates the same record from two different clients, each calling a different node
List<Callable<Void>> clients = new LinkedList<Callable<Void>>();
clients.add(new RecordUpdater(getDatabaseURL(serverInstance.get(0)), recordServer0, lukeFields, true));
clients.add(new RecordUpdater(getDatabaseURL(serverInstance.get(1)), recordServer1, darthFields, true));
List<Future<Void>> futures = Executors.newCachedThreadPool().invokeAll(clients);
executeFutures(futures);
// checks that record on server1 is discarded in favour of record present on server0
waitForUpdatedRecordPropagation(RECORD_ID, "firstName", lukeFields.get("firstName").toString());
recordServer0 = retrieveRecord(getDatabaseURL(serverInstance.get(0)), RECORD_ID);
recordServer1 = retrieveRecord(getDatabaseURL(serverInstance.get(1)), RECORD_ID);
int finalVersionServer0 = recordServer0.getVersion();
int finalVersionServer1 = recordServer1.getVersion();
assertEquals(finalVersionServer0, actualVersion + 1);
assertEquals(finalVersionServer1, actualVersion + 1);
}
@Override
public String getDatabaseName() {
return "distributed-simultaneous-update";
}
private void setWriteQuorum(int quorum) throws InterruptedException {
OHazelcastPlugin manager = (OHazelcastPlugin) serverInstance.get(0).getServerInstance().getDistributedManager();
OModifiableDistributedConfiguration databaseConfiguration = manager.getDatabaseConfiguration(getDatabaseName()).modify();
ODocument cfg = databaseConfiguration.getDocument();
cfg.field("writeQuorum", quorum);
manager.updateCachedDatabaseConfiguration(getDatabaseName(), databaseConfiguration, true);
Thread.sleep(100);
}
}