/*
* Copyright (c) 2008-2017, Hazelcast, Inc. 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.
*/
package com.hazelcast.internal.partition.operation;
import com.hazelcast.internal.partition.MigrationCycleOperation;
import com.hazelcast.internal.partition.MigrationInfo;
import com.hazelcast.internal.partition.impl.InternalPartitionServiceImpl;
import com.hazelcast.internal.partition.impl.PartitionReplicaManager;
import com.hazelcast.internal.partition.impl.PartitionStateManager;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.spi.MigrationAwareService;
import com.hazelcast.spi.PartitionAwareOperation;
import com.hazelcast.spi.PartitionMigrationEvent;
import com.hazelcast.spi.ServiceNamespace;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.partition.MigrationEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
/**
* Invoked locally on the source or destination of the migration to finalize the migration.
* This will notify the {@link MigrationAwareService}s that the migration finished, updates the replica versions,
* clears the migration flag and notifies the node engine when successful.
*/
public final class FinalizeMigrationOperation extends AbstractPartitionOperation
implements PartitionAwareOperation, MigrationCycleOperation {
private final MigrationInfo migrationInfo;
/** Defines if this node is the source or destination of the migration. */
private final MigrationEndpoint endpoint;
private final boolean success;
/**
* This constructor should not be used to obtain an instance of this class; it exists to fulfill IdentifiedDataSerializable
* coding conventions.
*/
public FinalizeMigrationOperation() {
migrationInfo = null;
endpoint = null;
success = false;
}
public FinalizeMigrationOperation(MigrationInfo migrationInfo, MigrationEndpoint endpoint, boolean success) {
this.migrationInfo = migrationInfo;
this.endpoint = endpoint;
this.success = success;
}
@Override
public void run() {
NodeEngineImpl nodeEngine = (NodeEngineImpl) getNodeEngine();
notifyServices(nodeEngine);
if (endpoint == MigrationEndpoint.SOURCE && success) {
commitSource();
} else if (endpoint == MigrationEndpoint.DESTINATION && !success) {
rollbackDestination();
}
InternalPartitionServiceImpl partitionService = getService();
PartitionStateManager partitionStateManager = partitionService.getPartitionStateManager();
partitionStateManager.clearMigratingFlag(migrationInfo.getPartitionId());
if (success) {
nodeEngine.onPartitionMigrate(migrationInfo);
}
}
/**
* Notifies all {@link MigrationAwareService}s that the migration finished. The services can then execute the commit or
* rollback logic. If this node was the source and backup replica for a partition, the services will first be notified that
* the migration is starting.
*/
private void notifyServices(NodeEngineImpl nodeEngine) {
PartitionMigrationEvent event = getPartitionMigrationEvent();
Collection<MigrationAwareService> migrationAwareServices = getMigrationAwareServices();
// Old backup owner is not notified about migration until migration
// is committed on destination. This is the only place on backup owner
// knows replica is moved away from itself.
if (nodeEngine.getThisAddress().equals(migrationInfo.getSource())
&& migrationInfo.getSourceCurrentReplicaIndex() > 0) {
// execute beforeMigration on old backup before commit/rollback
for (MigrationAwareService service : migrationAwareServices) {
beforeMigration(event, service);
}
}
for (MigrationAwareService service : migrationAwareServices) {
finishMigration(event, service);
}
}
private PartitionMigrationEvent getPartitionMigrationEvent() {
int partitionId = getPartitionId();
return new PartitionMigrationEvent(endpoint, partitionId,
endpoint == MigrationEndpoint.SOURCE
? migrationInfo.getSourceCurrentReplicaIndex() : migrationInfo.getDestinationCurrentReplicaIndex(),
endpoint == MigrationEndpoint.SOURCE
? migrationInfo.getSourceNewReplicaIndex() : migrationInfo.getDestinationNewReplicaIndex());
}
/** Updates the replica versions on the migration source if the replica index has changed. */
private void commitSource() {
int partitionId = getPartitionId();
InternalPartitionServiceImpl partitionService = getService();
PartitionReplicaManager replicaManager = partitionService.getReplicaManager();
ILogger logger = getLogger();
int sourceNewReplicaIndex = migrationInfo.getSourceNewReplicaIndex();
if (sourceNewReplicaIndex < 0) {
clearPartitionReplicaVersions(partitionId);
if (logger.isFinestEnabled()) {
logger.finest("Replica versions are cleared in source after migration. partitionId=" + partitionId);
}
} else if (migrationInfo.getSourceCurrentReplicaIndex() != sourceNewReplicaIndex && sourceNewReplicaIndex > 1) {
for (ServiceNamespace namespace : replicaManager.getNamespaces(partitionId)) {
long[] versions = updatePartitionReplicaVersions(replicaManager, partitionId,
namespace, sourceNewReplicaIndex - 1);
if (logger.isFinestEnabled()) {
logger.finest("Replica versions are set after SHIFT DOWN migration. partitionId="
+ partitionId + " namespace: " + namespace + " replica versions=" + Arrays.toString(versions));
}
}
}
}
private void clearPartitionReplicaVersions(int partitionId) {
InternalPartitionServiceImpl partitionService = getService();
PartitionReplicaManager replicaManager = partitionService.getReplicaManager();
for (ServiceNamespace namespace : replicaManager.getNamespaces(partitionId)) {
replicaManager.clearPartitionReplicaVersions(partitionId, namespace);
}
}
/** Updates the replica versions on the migration destination. */
private void rollbackDestination() {
int partitionId = getPartitionId();
InternalPartitionServiceImpl partitionService = getService();
PartitionReplicaManager replicaManager = partitionService.getReplicaManager();
ILogger logger = getLogger();
int destinationCurrentReplicaIndex = migrationInfo.getDestinationCurrentReplicaIndex();
if (destinationCurrentReplicaIndex == -1) {
clearPartitionReplicaVersions(partitionId);
if (logger.isFinestEnabled()) {
logger.finest("Replica versions are cleared in destination after failed migration. partitionId="
+ partitionId);
}
} else {
int replicaOffset = migrationInfo.getDestinationCurrentReplicaIndex() <= 1 ? 1 : migrationInfo
.getDestinationCurrentReplicaIndex();
for (ServiceNamespace namespace : replicaManager.getNamespaces(partitionId)) {
long[] versions = updatePartitionReplicaVersions(replicaManager, partitionId, namespace, replicaOffset - 1);
if (logger.isFinestEnabled()) {
logger.finest("Replica versions are rolled back in destination after failed migration. partitionId="
+ partitionId + " namespace: " + namespace + " replica versions=" + Arrays.toString(versions));
}
}
}
}
/** Sets all replica versions to {@code 0} up to the {@code replicaIndex}. */
private long[] updatePartitionReplicaVersions(PartitionReplicaManager replicaManager, int partitionId,
ServiceNamespace namespace, int replicaIndex) {
long[] versions = replicaManager.getPartitionReplicaVersions(partitionId, namespace);
// No need to set versions back right now. actual version array is modified directly.
Arrays.fill(versions, 0, replicaIndex, 0);
return versions;
}
private void beforeMigration(PartitionMigrationEvent event, MigrationAwareService service) {
try {
service.beforeMigration(event);
} catch (Throwable e) {
getLogger().warning("Error before migration -> " + event, e);
}
}
private void finishMigration(PartitionMigrationEvent event, MigrationAwareService service) {
try {
if (success) {
service.commitMigration(event);
} else {
service.rollbackMigration(event);
}
} catch (Throwable e) {
getLogger().warning("Error while finalizing migration -> " + event, e);
}
}
@Override
public boolean returnsResponse() {
return false;
}
@Override
public boolean validatesTarget() {
return false;
}
@Override
protected void readInternal(ObjectDataInput in) throws IOException {
throw new UnsupportedOperationException();
}
@Override
protected void writeInternal(ObjectDataOutput out) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int getId() {
throw new UnsupportedOperationException();
}
}