/*
* 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.InternalPartition;
import com.hazelcast.internal.partition.InternalPartitionService;
import com.hazelcast.internal.partition.MigrationInfo;
import com.hazelcast.internal.partition.NonFragmentedServiceNamespace;
import com.hazelcast.internal.partition.PartitionReplicaVersionManager;
import com.hazelcast.internal.partition.ReplicaFragmentMigrationState;
import com.hazelcast.internal.partition.impl.InternalPartitionServiceImpl;
import com.hazelcast.internal.partition.impl.MigrationManager;
import com.hazelcast.internal.partition.impl.PartitionDataSerializerHook;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.spi.FragmentedMigrationAwareService;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.PartitionReplicationEvent;
import com.hazelcast.spi.ServiceNamespace;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.PartitionSpecificRunnable;
import com.hazelcast.spi.impl.SimpleExecutionCallback;
import com.hazelcast.spi.impl.operationservice.InternalOperationService;
import com.hazelcast.spi.impl.servicemanager.ServiceInfo;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.singleton;
/**
* Migration request operation used by Hazelcast version 3.9
* Sent from the master node to the partition owner. It will perform the migration by preparing the migration operations and
* sending them to the destination. A response with a value equal to {@link Boolean#TRUE} indicates a successful migration.
* It runs on the migration source and transfers the partition with multiple shots.
* It divides the partition data into fragments and send a group of fragments within each shot.
*/
public class MigrationRequestOperation extends BaseMigrationSourceOperation {
private boolean fragmentedMigrationEnabled;
private transient ServiceNamespacesContext namespacesContext;
public MigrationRequestOperation() {
}
public MigrationRequestOperation(MigrationInfo migrationInfo, int partitionStateVersion,
boolean fragmentedMigrationEnabled) {
super(migrationInfo, partitionStateVersion);
this.fragmentedMigrationEnabled = fragmentedMigrationEnabled;
}
@Override
public void run() {
verifyMasterOnMigrationSource();
NodeEngineImpl nodeEngine = (NodeEngineImpl) getNodeEngine();
Address source = migrationInfo.getSource();
Address destination = migrationInfo.getDestination();
verifyExistingTarget(nodeEngine, destination);
if (destination.equals(source)) {
getLogger().warning("Source and destination addresses are the same! => " + toString());
setFailed();
return;
}
InternalPartition partition = getPartition();
verifySource(nodeEngine.getThisAddress(), partition);
setActiveMigration();
if (!migrationInfo.startProcessing()) {
getLogger().warning("Migration is cancelled -> " + migrationInfo);
setFailed();
return;
}
try {
executeBeforeMigrations();
namespacesContext = new ServiceNamespacesContext(nodeEngine, getPartitionReplicationEvent());
ReplicaFragmentMigrationState migrationState
= fragmentedMigrationEnabled ? createNextReplicaFragmentMigrationState()
: createAllReplicaFragmentsMigrationState();
invokeMigrationOperation(destination, migrationState, true);
returnResponse = false;
} catch (Throwable e) {
logThrowable(e);
setFailed();
} finally {
migrationInfo.doneProcessing();
}
}
/**
* Invokes the {@link MigrationOperation} on the migration destination.
*/
private void invokeMigrationOperation(Address destination, ReplicaFragmentMigrationState migrationState,
boolean firstFragment) throws IOException {
boolean lastFragment = !fragmentedMigrationEnabled || !namespacesContext.hasNext();
Operation operation = new MigrationOperation(migrationInfo, partitionStateVersion, migrationState,
firstFragment, lastFragment);
ILogger logger = getLogger();
if (logger.isFinestEnabled()) {
Set<ServiceNamespace> namespaces = migrationState != null
? migrationState.getNamespaceVersionMap().keySet() : Collections.<ServiceNamespace>emptySet();
logger.finest("Invoking MigrationOperation for namespaces " + namespaces + " and " + migrationInfo
+ ", lastFragment: " + lastFragment);
}
NodeEngine nodeEngine = getNodeEngine();
InternalPartitionServiceImpl partitionService = getService();
nodeEngine.getOperationService()
.createInvocationBuilder(InternalPartitionService.SERVICE_NAME, operation, destination)
.setExecutionCallback(new MigrationCallback())
.setResultDeserialized(true)
.setCallTimeout(partitionService.getPartitionMigrationTimeout())
.setTryCount(InternalPartitionService.MIGRATION_RETRY_COUNT)
.setTryPauseMillis(InternalPartitionService.MIGRATION_RETRY_PAUSE)
.setReplicaIndex(getReplicaIndex())
.invoke();
}
private void trySendNewFragment() {
try {
assert fragmentedMigrationEnabled : "Fragmented migration should be enabled!";
verifyMasterOnMigrationSource();
NodeEngine nodeEngine = getNodeEngine();
Address destination = migrationInfo.getDestination();
verifyExistingTarget(nodeEngine, destination);
InternalPartitionServiceImpl partitionService = getService();
MigrationManager migrationManager = partitionService.getMigrationManager();
MigrationInfo currentActiveMigration = migrationManager.setActiveMigration(migrationInfo);
if (!migrationInfo.equals(currentActiveMigration)) {
throw new IllegalStateException("Current active migration " + currentActiveMigration
+ " is different than expected: " + migrationInfo);
}
ReplicaFragmentMigrationState migrationState = createNextReplicaFragmentMigrationState();
if (migrationState != null) {
invokeMigrationOperation(destination, migrationState, false);
} else {
getLogger().finest("All migration fragments done for " + migrationInfo);
completeMigration(true);
}
} catch (Throwable e) {
logThrowable(e);
completeMigration(false);
}
}
private ReplicaFragmentMigrationState createNextReplicaFragmentMigrationState() {
assert fragmentedMigrationEnabled : "Fragmented migration should be enabled!";
if (!namespacesContext.hasNext()) {
return null;
}
ServiceNamespace namespace = namespacesContext.next();
if (namespace.equals(NonFragmentedServiceNamespace.INSTANCE)) {
return createNonFragmentedReplicaFragmentMigrationState();
}
return createReplicaFragmentMigrationStateFor(namespace);
}
private ReplicaFragmentMigrationState createNonFragmentedReplicaFragmentMigrationState() {
PartitionReplicationEvent event = getPartitionReplicationEvent();
Collection<Operation> operations = createNonFragmentedReplicationOperations(event);
Collection<ServiceNamespace> namespaces =
Collections.<ServiceNamespace>singleton(NonFragmentedServiceNamespace.INSTANCE);
return createReplicaFragmentMigrationState(namespaces, operations);
}
private ReplicaFragmentMigrationState createReplicaFragmentMigrationStateFor(ServiceNamespace ns) {
PartitionReplicationEvent event = getPartitionReplicationEvent();
Collection<String> serviceNames = namespacesContext.getServiceNames(ns);
Collection<Operation> operations = createFragmentReplicationOperations(event, ns, serviceNames);
return createReplicaFragmentMigrationState(singleton(ns), operations);
}
private ReplicaFragmentMigrationState createAllReplicaFragmentsMigrationState() {
PartitionReplicationEvent event = getPartitionReplicationEvent();
Collection<Operation> operations = createAllReplicationOperations(event);
return createReplicaFragmentMigrationState(namespacesContext.allNamespaces, operations);
}
private ReplicaFragmentMigrationState createReplicaFragmentMigrationState(Collection<ServiceNamespace>
namespaces, Collection<Operation> operations) {
InternalPartitionService partitionService = getService();
PartitionReplicaVersionManager versionManager = partitionService.getPartitionReplicaVersionManager();
Map<ServiceNamespace, long[]> versions = new HashMap<ServiceNamespace, long[]>(namespaces.size());
for (ServiceNamespace namespace : namespaces) {
long[] v = versionManager.getPartitionReplicaVersions(getPartitionId(), namespace);
versions.put(namespace, v);
}
return new ReplicaFragmentMigrationState(versions, operations);
}
@Override
public int getId() {
return PartitionDataSerializerHook.MIGRATION_REQUEST;
}
@Override
protected void writeInternal(ObjectDataOutput out) throws IOException {
super.writeInternal(out);
out.writeBoolean(fragmentedMigrationEnabled);
}
@Override
protected void readInternal(ObjectDataInput in) throws IOException {
super.readInternal(in);
fragmentedMigrationEnabled = in.readBoolean();
}
/**
* Processes the migration result sent from the migration destination and sends the response to the caller of this operation.
* A response equal to {@link Boolean#TRUE} indicates successful migration.
*/
private final class MigrationCallback extends SimpleExecutionCallback<Object> {
private MigrationCallback() {
}
@Override
public void notify(Object result) {
if (Boolean.TRUE.equals(result)) {
if (fragmentedMigrationEnabled) {
InternalOperationService operationService = (InternalOperationService) getNodeEngine().getOperationService();
operationService.execute(new SendNewMigrationFragmentRunnable());
} else {
completeMigration(true);
}
} else {
completeMigration(false);
}
}
}
private final class SendNewMigrationFragmentRunnable implements PartitionSpecificRunnable {
@Override
public int getPartitionId() {
return MigrationRequestOperation.this.getPartitionId();
}
@Override
public void run() {
trySendNewFragment();
}
}
private static class ServiceNamespacesContext {
final Collection<ServiceNamespace> allNamespaces = new HashSet<ServiceNamespace>();
final Map<ServiceNamespace, Collection<String>> namespaceToServices = new HashMap<ServiceNamespace, Collection<String>>();
final Iterator<ServiceNamespace> namespaceIterator;
ServiceNamespacesContext(NodeEngineImpl nodeEngine, PartitionReplicationEvent event) {
Collection<ServiceInfo> services = nodeEngine.getServiceInfos(FragmentedMigrationAwareService.class);
for (ServiceInfo serviceInfo : services) {
FragmentedMigrationAwareService service = serviceInfo.getService();
Collection<ServiceNamespace> namespaces = service.getAllServiceNamespaces(event);
if (namespaces != null) {
String serviceName = serviceInfo.getName();
allNamespaces.addAll(namespaces);
addNamespaceToServiceMappings(namespaces, serviceName);
}
}
allNamespaces.add(NonFragmentedServiceNamespace.INSTANCE);
namespaceIterator = allNamespaces.iterator();
}
private void addNamespaceToServiceMappings(Collection<ServiceNamespace> namespaces, String serviceName) {
for (ServiceNamespace ns : namespaces) {
Collection<String> serviceNames = namespaceToServices.get(ns);
if (serviceNames == null) {
// generally a namespace belongs to a single service only
namespaceToServices.put(ns, singleton(serviceName));
} else if (serviceNames.size() == 1) {
serviceNames = new HashSet<String>(serviceNames);
serviceNames.add(serviceName);
namespaceToServices.put(ns, serviceNames);
} else {
serviceNames.add(serviceName);
}
}
}
boolean hasNext() {
return namespaceIterator.hasNext();
}
ServiceNamespace next() {
return namespaceIterator.next();
}
Collection<String> getServiceNames(ServiceNamespace ns) {
return namespaceToServices.get(ns);
}
}
}