mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
New feature: Reconcile commands (CopyCommand, MigrateCommand, MigrateVolumeCommand) (#10514)
This commit is contained in:
parent
d7d9d131b2
commit
fd74895ad0
@ -800,6 +800,9 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater
|
|||||||
}
|
}
|
||||||
commandsInProgress.incrementAndGet();
|
commandsInProgress.incrementAndGet();
|
||||||
try {
|
try {
|
||||||
|
if (cmd.isReconcile()) {
|
||||||
|
cmd.setRequestSequence(request.getSequence());
|
||||||
|
}
|
||||||
answer = serverResource.executeRequest(cmd);
|
answer = serverResource.executeRequest(cmd);
|
||||||
} finally {
|
} finally {
|
||||||
commandsInProgress.decrementAndGet();
|
commandsInProgress.decrementAndGet();
|
||||||
@ -1021,6 +1024,8 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater
|
|||||||
if ((answer.isSendStartup()) && reconnectAllowed) {
|
if ((answer.isSendStartup()) && reconnectAllowed) {
|
||||||
logger.info("Management server requested startup command to reinitialize the agent");
|
logger.info("Management server requested startup command to reinitialize the agent");
|
||||||
sendStartup(link);
|
sendStartup(link);
|
||||||
|
} else {
|
||||||
|
serverResource.processPingAnswer((PingAnswer) answer);
|
||||||
}
|
}
|
||||||
shell.setAvoidHosts(answer.getAvoidMsList());
|
shell.setAvoidHosts(answer.getAvoidMsList());
|
||||||
}
|
}
|
||||||
@ -1087,6 +1092,9 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater
|
|||||||
Answer answer = null;
|
Answer answer = null;
|
||||||
commandsInProgress.incrementAndGet();
|
commandsInProgress.incrementAndGet();
|
||||||
try {
|
try {
|
||||||
|
if (command.isReconcile()) {
|
||||||
|
command.setRequestSequence(req.getSequence());
|
||||||
|
}
|
||||||
answer = serverResource.executeRequest(command);
|
answer = serverResource.executeRequest(command);
|
||||||
} finally {
|
} finally {
|
||||||
commandsInProgress.decrementAndGet();
|
commandsInProgress.decrementAndGet();
|
||||||
|
|||||||
@ -35,6 +35,23 @@ public abstract class Command {
|
|||||||
Continue, Stop
|
Continue, Stop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
CREATED, // Command is created by management server
|
||||||
|
STARTED, // Command is started by agent
|
||||||
|
PROCESSING, // Processing by agent
|
||||||
|
PROCESSING_IN_BACKEND, // Processing in backend by agent
|
||||||
|
COMPLETED, // Operation succeeds by agent or management server
|
||||||
|
FAILED, // Operation fails by agent
|
||||||
|
RECONCILE_RETRY, // Ready for retry of reconciliation
|
||||||
|
RECONCILING, // Being reconciled by management server
|
||||||
|
RECONCILED, // Reconciled by management server
|
||||||
|
RECONCILE_SKIPPED, // Skip the reconciliation as the resource state is inconsistent with the command
|
||||||
|
RECONCILE_FAILED, // Fail to reconcile by management server
|
||||||
|
TIMED_OUT, // Timed out on management server or agent
|
||||||
|
INTERRUPTED, // Interrupted by management server or agent (for example agent is restarted),
|
||||||
|
DANGLED_IN_BACKEND // Backend process which cannot be processed normally (for example agent is restarted)
|
||||||
|
}
|
||||||
|
|
||||||
public static final String HYPERVISOR_TYPE = "hypervisorType";
|
public static final String HYPERVISOR_TYPE = "hypervisorType";
|
||||||
|
|
||||||
// allow command to carry over hypervisor or other environment related context info
|
// allow command to carry over hypervisor or other environment related context info
|
||||||
@ -42,6 +59,7 @@ public abstract class Command {
|
|||||||
protected Map<String, String> contextMap = new HashMap<String, String>();
|
protected Map<String, String> contextMap = new HashMap<String, String>();
|
||||||
private int wait; //in second
|
private int wait; //in second
|
||||||
private boolean bypassHostMaintenance = false;
|
private boolean bypassHostMaintenance = false;
|
||||||
|
private transient long requestSequence = 0L;
|
||||||
|
|
||||||
protected Command() {
|
protected Command() {
|
||||||
this.wait = 0;
|
this.wait = 0;
|
||||||
@ -82,6 +100,10 @@ public abstract class Command {
|
|||||||
return contextMap.get(name);
|
return contextMap.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getContextMap() {
|
||||||
|
return contextMap;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean allowCaching() {
|
public boolean allowCaching() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -94,6 +116,18 @@ public abstract class Command {
|
|||||||
this.bypassHostMaintenance = bypassHostMaintenance;
|
this.bypassHostMaintenance = bypassHostMaintenance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isReconcile() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestSequence() {
|
||||||
|
return requestSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestSequence(long requestSequence) {
|
||||||
|
this.requestSequence = requestSequence;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public class DiskTO {
|
|||||||
private Long diskSeq;
|
private Long diskSeq;
|
||||||
private String path;
|
private String path;
|
||||||
private Volume.Type type;
|
private Volume.Type type;
|
||||||
private Map<String, String> _details;
|
private Map<String, String> details;
|
||||||
|
|
||||||
public DiskTO() {
|
public DiskTO() {
|
||||||
|
|
||||||
@ -92,10 +92,10 @@ public class DiskTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDetails(Map<String, String> details) {
|
public void setDetails(Map<String, String> details) {
|
||||||
_details = details;
|
this.details = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getDetails() {
|
public Map<String, String> getDetails() {
|
||||||
return _details;
|
return details;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public class NetworkTO {
|
|||||||
protected TrafficType type;
|
protected TrafficType type;
|
||||||
protected URI broadcastUri;
|
protected URI broadcastUri;
|
||||||
protected URI isolationUri;
|
protected URI isolationUri;
|
||||||
protected boolean isSecurityGroupEnabled;
|
protected boolean securityGroupEnabled;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String ip6address;
|
protected String ip6address;
|
||||||
protected String ip6gateway;
|
protected String ip6gateway;
|
||||||
@ -112,7 +112,7 @@ public class NetworkTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSecurityGroupEnabled(boolean enabled) {
|
public void setSecurityGroupEnabled(boolean enabled) {
|
||||||
this.isSecurityGroupEnabled = enabled;
|
this.securityGroupEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,7 +221,7 @@ public class NetworkTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecurityGroupEnabled() {
|
public boolean isSecurityGroupEnabled() {
|
||||||
return this.isSecurityGroupEnabled;
|
return this.securityGroupEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIp6Dns1(String ip6Dns1) {
|
public void setIp6Dns1(String ip6Dns1) {
|
||||||
|
|||||||
@ -86,6 +86,14 @@ public class NicTO extends NetworkTO {
|
|||||||
this.nicUuid = uuid;
|
this.nicUuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNicUuid() {
|
||||||
|
return nicUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNicUuid(String nicUuid) {
|
||||||
|
this.nicUuid = nicUuid;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new StringBuilder("[Nic:").append(type).append("-").append(ip).append("-").append(broadcastUri).append("]").toString();
|
return new StringBuilder("[Nic:").append(type).append("-").append(ip).append("-").append(broadcastUri).append("]").toString();
|
||||||
|
|||||||
@ -61,7 +61,7 @@ public class VirtualMachineTO {
|
|||||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||||
String vncPassword;
|
String vncPassword;
|
||||||
String vncAddr;
|
String vncAddr;
|
||||||
Map<String, String> params;
|
Map<String, String> details;
|
||||||
String uuid;
|
String uuid;
|
||||||
String bootType;
|
String bootType;
|
||||||
String bootMode;
|
String bootMode;
|
||||||
@ -191,7 +191,11 @@ public class VirtualMachineTO {
|
|||||||
return maxSpeed;
|
return maxSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getLimitCpuUse() {
|
public boolean isEnableHA() {
|
||||||
|
return enableHA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLimitCpuUse() {
|
||||||
return limitCpuUse;
|
return limitCpuUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,11 +293,11 @@ public class VirtualMachineTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getDetails() {
|
public Map<String, String> getDetails() {
|
||||||
return params;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDetails(Map<String, String> params) {
|
public void setDetails(Map<String, String> params) {
|
||||||
this.params = params;
|
this.details = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUuid() {
|
public String getUuid() {
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ReconcileCommandService {
|
||||||
|
|
||||||
|
ConfigKey<Boolean> ReconcileCommandsEnabled = new ConfigKey<>("Advanced", Boolean.class,
|
||||||
|
"reconcile.commands.enabled", "false",
|
||||||
|
"Indicates whether the background task to reconcile the commands is enabled or not",
|
||||||
|
false);
|
||||||
|
|
||||||
|
ConfigKey<Integer> ReconcileCommandsInterval = new ConfigKey<>("Advanced", Integer.class,
|
||||||
|
"reconcile.commands.interval", "60",
|
||||||
|
"Interval (in seconds) for the background task to reconcile the commands",
|
||||||
|
false);
|
||||||
|
ConfigKey<Integer> ReconcileCommandsMaxAttempts = new ConfigKey<>("Advanced", Integer.class,
|
||||||
|
"reconcile.commands.max.attempts", "30",
|
||||||
|
"The maximum number of attempts to reconcile the commands",
|
||||||
|
true);
|
||||||
|
|
||||||
|
ConfigKey<Integer> ReconcileCommandsWorkers = new ConfigKey<>("Advanced", Integer.class,
|
||||||
|
"reconcile.commands.workers", "100",
|
||||||
|
"The Number of worker threads to reconcile the commands",
|
||||||
|
false);
|
||||||
|
|
||||||
|
List<Hypervisor.HypervisorType> SupportedHypervisorTypes = Arrays.asList(Hypervisor.HypervisorType.KVM);
|
||||||
|
|
||||||
|
void persistReconcileCommands(Long hostId, Long requestSequence, Command[] cmd);
|
||||||
|
|
||||||
|
boolean updateReconcileCommand(long requestSeq, Command command, Answer answer, Command.State newStateByManagement, Command.State newStateByAgent);
|
||||||
|
|
||||||
|
void processCommand(Command pingCommand, Answer pingAnswer);
|
||||||
|
|
||||||
|
void processAnswers(long requestSeq, Command[] commands, Answer[] answers);
|
||||||
|
|
||||||
|
void updateReconcileCommandToInterruptedByManagementServerId(long managementServerId);
|
||||||
|
|
||||||
|
void updateReconcileCommandToInterruptedByHostId(long hostId);
|
||||||
|
|
||||||
|
boolean isReconcileResourceNeeded(long resourceId, ApiCommandResourceType resourceType);
|
||||||
|
}
|
||||||
@ -29,14 +29,14 @@ import com.cloud.agent.api.to.VirtualMachineTO;
|
|||||||
|
|
||||||
public class MigrateCommand extends Command {
|
public class MigrateCommand extends Command {
|
||||||
private String vmName;
|
private String vmName;
|
||||||
private String destIp;
|
private String destinationIp;
|
||||||
private Map<String, MigrateDiskInfo> migrateStorage;
|
private Map<String, MigrateDiskInfo> migrateStorage;
|
||||||
private boolean migrateStorageManaged;
|
private boolean migrateStorageManaged;
|
||||||
private boolean migrateNonSharedInc;
|
private boolean migrateNonSharedInc;
|
||||||
private boolean autoConvergence;
|
private boolean autoConvergence;
|
||||||
private String hostGuid;
|
private String hostGuid;
|
||||||
private boolean isWindows;
|
private boolean windows;
|
||||||
private VirtualMachineTO vmTO;
|
private VirtualMachineTO virtualMachine;
|
||||||
private boolean executeInSequence = false;
|
private boolean executeInSequence = false;
|
||||||
private List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
|
private List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
|
||||||
private Map<String, DpdkTO> dpdkInterfaceMapping = new HashMap<>();
|
private Map<String, DpdkTO> dpdkInterfaceMapping = new HashMap<>();
|
||||||
@ -64,11 +64,11 @@ public class MigrateCommand extends Command {
|
|||||||
protected MigrateCommand() {
|
protected MigrateCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public MigrateCommand(String vmName, String destIp, boolean isWindows, VirtualMachineTO vmTO, boolean executeInSequence) {
|
public MigrateCommand(String vmName, String destinationIp, boolean windows, VirtualMachineTO virtualMachine, boolean executeInSequence) {
|
||||||
this.vmName = vmName;
|
this.vmName = vmName;
|
||||||
this.destIp = destIp;
|
this.destinationIp = destinationIp;
|
||||||
this.isWindows = isWindows;
|
this.windows = windows;
|
||||||
this.vmTO = vmTO;
|
this.virtualMachine = virtualMachine;
|
||||||
this.executeInSequence = executeInSequence;
|
this.executeInSequence = executeInSequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,15 +105,15 @@ public class MigrateCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWindows() {
|
public boolean isWindows() {
|
||||||
return isWindows;
|
return windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VirtualMachineTO getVirtualMachine() {
|
public VirtualMachineTO getVirtualMachine() {
|
||||||
return vmTO;
|
return virtualMachine;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDestinationIp() {
|
public String getDestinationIp() {
|
||||||
return destIp;
|
return destinationIp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVmName() {
|
public String getVmName() {
|
||||||
@ -233,4 +233,9 @@ public class MigrateCommand extends Command {
|
|||||||
this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage;
|
this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReconcile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.cloud.agent.api;
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PingAnswer extends Answer {
|
public class PingAnswer extends Answer {
|
||||||
@ -27,6 +28,8 @@ public class PingAnswer extends Answer {
|
|||||||
private boolean sendStartup = false;
|
private boolean sendStartup = false;
|
||||||
private List<String> avoidMsList;
|
private List<String> avoidMsList;
|
||||||
|
|
||||||
|
private List<String> reconcileCommands = new ArrayList<>();
|
||||||
|
|
||||||
protected PingAnswer() {
|
protected PingAnswer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +52,18 @@ public class PingAnswer extends Answer {
|
|||||||
this.sendStartup = sendStartup;
|
this.sendStartup = sendStartup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getReconcileCommands() {
|
||||||
|
return reconcileCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReconcileCommands(List<String> reconcileCommands) {
|
||||||
|
this.reconcileCommands = reconcileCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addReconcileCommand(String reconcileCommand) {
|
||||||
|
this.reconcileCommands.add(reconcileCommand);
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getAvoidMsList() {
|
public List<String> getAvoidMsList() {
|
||||||
return avoidMsList;
|
return avoidMsList;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,11 +20,14 @@
|
|||||||
package com.cloud.agent.api;
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
import com.cloud.host.Host;
|
import com.cloud.host.Host;
|
||||||
|
import org.apache.cloudstack.command.CommandInfo;
|
||||||
|
|
||||||
public class PingCommand extends Command {
|
public class PingCommand extends Command {
|
||||||
Host.Type hostType;
|
Host.Type hostType;
|
||||||
long hostId;
|
long hostId;
|
||||||
boolean outOfBand;
|
boolean outOfBand;
|
||||||
|
@LogLevel(LogLevel.Log4jLevel.Trace)
|
||||||
|
private CommandInfo[] commandInfos = new CommandInfo[] {};
|
||||||
|
|
||||||
protected PingCommand() {
|
protected PingCommand() {
|
||||||
}
|
}
|
||||||
@ -78,4 +81,12 @@ public class PingCommand extends Command {
|
|||||||
result = 31 * result + (int) (hostId ^ (hostId >>> 32));
|
result = 31 * result + (int) (hostId ^ (hostId >>> 32));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandInfo[] getCommandInfos() {
|
||||||
|
return commandInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandInfos(CommandInfo[] commandInfos) {
|
||||||
|
this.commandInfos = commandInfos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,4 +145,9 @@ public class MigrateVolumeCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getChainInfo() { return chainInfo; }
|
public String getChainInfo() { return chainInfo; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReconcile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ package com.cloud.resource;
|
|||||||
import com.cloud.agent.IAgentControl;
|
import com.cloud.agent.IAgentControl;
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
import com.cloud.agent.api.Command;
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.agent.api.PingAnswer;
|
||||||
import com.cloud.agent.api.PingCommand;
|
import com.cloud.agent.api.PingCommand;
|
||||||
import com.cloud.agent.api.StartupCommand;
|
import com.cloud.agent.api.StartupCommand;
|
||||||
import com.cloud.host.Host;
|
import com.cloud.host.Host;
|
||||||
@ -90,4 +91,5 @@ public interface ServerResource extends Manager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void processPingAnswer(PingAnswer answer) {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class GsonHelper {
|
|||||||
LOGGER.info("Default Builder inited.");
|
LOGGER.info("Default Builder inited.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Gson setDefaultGsonConfig(GsonBuilder builder) {
|
public static Gson setDefaultGsonConfig(GsonBuilder builder) {
|
||||||
builder.setVersion(1.5);
|
builder.setVersion(1.5);
|
||||||
InterfaceTypeAdaptor<DataStoreTO> dsAdaptor = new InterfaceTypeAdaptor<DataStoreTO>();
|
InterfaceTypeAdaptor<DataStoreTO> dsAdaptor = new InterfaceTypeAdaptor<DataStoreTO>();
|
||||||
builder.registerTypeAdapter(DataStoreTO.class, dsAdaptor);
|
builder.registerTypeAdapter(DataStoreTO.class, dsAdaptor);
|
||||||
|
|||||||
@ -0,0 +1,124 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.serializer.GsonHelper;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class CommandInfo {
|
||||||
|
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSZ";
|
||||||
|
public static final Gson GSON = GsonHelper.setDefaultGsonConfig(new GsonBuilder().setDateFormat(DATE_FORMAT));
|
||||||
|
|
||||||
|
long requestSeq;
|
||||||
|
Command.State state;
|
||||||
|
Date startTime;
|
||||||
|
Date updateTime;
|
||||||
|
String commandName;
|
||||||
|
String command;
|
||||||
|
int timeout;
|
||||||
|
String answerName;
|
||||||
|
String answer;
|
||||||
|
|
||||||
|
public CommandInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandInfo(long requestSeq, Command command, Command.State state) {
|
||||||
|
this.requestSeq = requestSeq;
|
||||||
|
this.state = state;
|
||||||
|
this.startTime = this.updateTime = new Date();
|
||||||
|
this.commandName = command.getClass().getName();
|
||||||
|
this.command = GSON.toJson(command);
|
||||||
|
this.timeout = command.getWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestSeq() {
|
||||||
|
return requestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestSeq(long requestSeq) {
|
||||||
|
this.requestSeq = requestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command.State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(Command.State state) {
|
||||||
|
this.state = state;
|
||||||
|
this.updateTime = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTime(Date updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommandName() {
|
||||||
|
return commandName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandName(String commandName) {
|
||||||
|
this.commandName = commandName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommand(String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(int timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswerName() {
|
||||||
|
return answerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswerName(String answerName) {
|
||||||
|
this.answerName = answerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswer() {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswer(String answer) {
|
||||||
|
this.answer = answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Answer;
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
|
|
||||||
|
public class ReconcileAnswer extends Answer {
|
||||||
|
|
||||||
|
ApiCommandResourceType resourceType;
|
||||||
|
Long resourceId;
|
||||||
|
|
||||||
|
public ApiCommandResourceType getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(ApiCommandResourceType resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(Long resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
|
||||||
|
public class ReconcileCommand extends Command {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean executeInSequence() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWait() {
|
||||||
|
return 30; // timeout is 30 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
public class ReconcileCommandUtils {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = LogManager.getLogger(ReconcileCommandUtils.class.getName());
|
||||||
|
|
||||||
|
public static void createLogFileForCommand(final String logPath, final Command cmd) {
|
||||||
|
updateLogFileForCommand(logPath, cmd, Command.State.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateLogFileForCommand(final String logPath, final Command cmd, final Command.State state) {
|
||||||
|
if (cmd.isReconcile()) {
|
||||||
|
String logFileName = getLogFileNameForCommand(logPath, cmd);
|
||||||
|
LOGGER.debug(String.format("Updating log file %s with %s state", logFileName, state));
|
||||||
|
File logFile = new File(logFileName);
|
||||||
|
CommandInfo commandInfo = null;
|
||||||
|
if (logFile.exists()) {
|
||||||
|
commandInfo = readLogFileForCommand(logFileName);
|
||||||
|
logFile.delete();
|
||||||
|
}
|
||||||
|
if (commandInfo == null) {
|
||||||
|
commandInfo = new CommandInfo(cmd.getRequestSequence(), cmd, state);
|
||||||
|
} else {
|
||||||
|
commandInfo.setState(state);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
BufferedWriter writer = new BufferedWriter(new FileWriter(logFile));
|
||||||
|
writer.write(CommandInfo.GSON.toJson(commandInfo));
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(String.format("Failed to write log file %s", logFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateLogFileForCommand(final String logFullPath, final Command.State state) {
|
||||||
|
File logFile = new File(logFullPath);
|
||||||
|
LOGGER.debug(String.format("Updating log file %s with %s state", logFile.getName(), state));
|
||||||
|
if (!logFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CommandInfo commandInfo = readLogFileForCommand(logFullPath);
|
||||||
|
if (commandInfo != null) {
|
||||||
|
commandInfo.setState(state);
|
||||||
|
}
|
||||||
|
logFile.delete();
|
||||||
|
try {
|
||||||
|
BufferedWriter writer = new BufferedWriter(new FileWriter(logFile));
|
||||||
|
writer.write(CommandInfo.GSON.toJson(commandInfo));
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(String.format("Failed to write log file %s", logFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteLogFileForCommand(final String logPath, final Command cmd) {
|
||||||
|
if (cmd.isReconcile()) {
|
||||||
|
File logFile = new File(getLogFileNameForCommand(logPath, cmd));
|
||||||
|
LOGGER.debug(String.format("Removing log file %s", logFile.getName()));
|
||||||
|
if (logFile.exists()) {
|
||||||
|
logFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteLogFile(final String logFullPath) {
|
||||||
|
File logFile = new File(logFullPath);
|
||||||
|
LOGGER.debug(String.format("Removing log file %s ", logFile.getName()));
|
||||||
|
if (logFile.exists()) {
|
||||||
|
logFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLogFileNameForCommand(final String logPath, final Command cmd) {
|
||||||
|
return String.format("%s/%s-%s.json", logPath, cmd.getRequestSequence(), cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CommandInfo readLogFileForCommand(final String logFullPath) {
|
||||||
|
try {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat(CommandInfo.DATE_FORMAT);
|
||||||
|
objectMapper.setDateFormat(df);
|
||||||
|
return objectMapper.readValue(new File(logFullPath), CommandInfo.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(String.format("Failed to read log file %s: %s", logFullPath, e.getMessage()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Command parseCommandInfo(final CommandInfo commandInfo) {
|
||||||
|
if (commandInfo.getCommandName() == null || commandInfo.getCommand() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseCommandInfo(commandInfo.getCommandName(), commandInfo.getCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Command parseCommandInfo(final String commandName, final String commandInfo) {
|
||||||
|
Object parsedObject = null;
|
||||||
|
try {
|
||||||
|
Class<?> commandClazz = Class.forName(commandName);
|
||||||
|
parsedObject = CommandInfo.GSON.fromJson(commandInfo, commandClazz);
|
||||||
|
} catch (ClassNotFoundException | JsonSyntaxException e) {
|
||||||
|
LOGGER.error(String.format("Failed to parse command from CommandInfo %s due to %s", commandInfo, e.getMessage()));
|
||||||
|
}
|
||||||
|
if (parsedObject != null) {
|
||||||
|
return (Command) parsedObject;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Answer parseAnswerFromCommandInfo(final CommandInfo commandInfo) {
|
||||||
|
if (commandInfo.getAnswerName() == null || commandInfo.getAnswer() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseAnswerFromAnswerInfo(commandInfo.getAnswerName(), commandInfo.getAnswer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Answer parseAnswerFromAnswerInfo(final String answerName, final String answerInfo) {
|
||||||
|
Object parsedObject = null;
|
||||||
|
try {
|
||||||
|
Class<?> commandClazz = Class.forName(answerName);
|
||||||
|
parsedObject = CommandInfo.GSON.fromJson(answerInfo, commandClazz);
|
||||||
|
} catch (ClassNotFoundException | JsonSyntaxException e) {
|
||||||
|
LOGGER.error(String.format("Failed to parse answer from answerInfo %s due to %s", answerInfo, e.getMessage()));
|
||||||
|
}
|
||||||
|
if (parsedObject != null) {
|
||||||
|
return (Answer) parsedObject;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateLogFileWithAnswerForCommand(final String logPath, final Command cmd, final Answer answer) {
|
||||||
|
if (cmd.isReconcile()) {
|
||||||
|
String logFileName = getLogFileNameForCommand(logPath, cmd);
|
||||||
|
LOGGER.debug(String.format("Updating log file %s with answer %s", logFileName, answer));
|
||||||
|
File logFile = new File(logFileName);
|
||||||
|
if (!logFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CommandInfo commandInfo = readLogFileForCommand(logFile.getAbsolutePath());
|
||||||
|
if (commandInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Command.State.STARTED.equals(commandInfo.getState())) {
|
||||||
|
if (answer.getResult()) {
|
||||||
|
commandInfo.setState(Command.State.COMPLETED);
|
||||||
|
} else {
|
||||||
|
commandInfo.setState(Command.State.FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commandInfo.setAnswerName(answer.toString());
|
||||||
|
commandInfo.setAnswer(CommandInfo.GSON.toJson(answer));
|
||||||
|
try {
|
||||||
|
BufferedWriter writer = new BufferedWriter(new FileWriter(logFile));
|
||||||
|
writer.write(CommandInfo.GSON.toJson(commandInfo));
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(String.format("Failed to write log file %s", logFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
|
||||||
|
|
||||||
|
public class ReconcileCopyAnswer extends ReconcileVolumeAnswer {
|
||||||
|
|
||||||
|
boolean isSkipped = false;
|
||||||
|
String reason;
|
||||||
|
|
||||||
|
public ReconcileCopyAnswer(boolean isSkipped, String reason) {
|
||||||
|
super();
|
||||||
|
this.isSkipped = isSkipped;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReconcileCopyAnswer(boolean isSkipped, boolean result, String reason) {
|
||||||
|
super();
|
||||||
|
this.isSkipped = isSkipped;
|
||||||
|
this.result = result;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReconcileCopyAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) {
|
||||||
|
this.isSkipped = false;
|
||||||
|
this.result = true;
|
||||||
|
this.volumeOnSource = volumeOnSource;
|
||||||
|
this.volumeOnDestination = volumeOnDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSkipped() {
|
||||||
|
return isSkipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.to.DataTO;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ReconcileCopyCommand extends ReconcileCommand {
|
||||||
|
|
||||||
|
DataTO srcData;
|
||||||
|
DataTO destData;
|
||||||
|
Map<String, String> option; // details of source volume
|
||||||
|
Map<String, String> option2; // details of destination volume
|
||||||
|
|
||||||
|
public ReconcileCopyCommand(DataTO srcData, DataTO destData, Map<String, String> option, Map<String, String> option2) {
|
||||||
|
this.srcData = srcData;
|
||||||
|
this.destData = destData;
|
||||||
|
this.option = option;
|
||||||
|
this.option2 = option2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTO getSrcData() {
|
||||||
|
return srcData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTO getDestData() {
|
||||||
|
return destData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getOption() {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getOption2() {
|
||||||
|
return option2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ReconcileMigrateAnswer extends ReconcileAnswer {
|
||||||
|
|
||||||
|
Long hostId;
|
||||||
|
String vmName;
|
||||||
|
VirtualMachine.State vmState;
|
||||||
|
List<String> vmDisks;
|
||||||
|
|
||||||
|
public ReconcileMigrateAnswer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReconcileMigrateAnswer(String vmName, VirtualMachine.State vmState) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
this.vmState = vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getHostId() {
|
||||||
|
return hostId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostId(Long hostId) {
|
||||||
|
this.hostId = hostId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualMachine.State getVmState() {
|
||||||
|
return vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmState(VirtualMachine.State vmState) {
|
||||||
|
this.vmState = vmState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVmDisks() {
|
||||||
|
return vmDisks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmDisks(List<String> vmDisks) {
|
||||||
|
this.vmDisks = vmDisks;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
public class ReconcileMigrateCommand extends ReconcileCommand {
|
||||||
|
|
||||||
|
String vmName;
|
||||||
|
|
||||||
|
public ReconcileMigrateCommand(String vmName) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ReconcileMigrateVolumeAnswer extends ReconcileVolumeAnswer {
|
||||||
|
|
||||||
|
String vmName;
|
||||||
|
List<String> vmDiskPaths;
|
||||||
|
|
||||||
|
public ReconcileMigrateVolumeAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) {
|
||||||
|
super(volumeOnSource, volumeOnDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmName(String vmName) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVmDiskPaths() {
|
||||||
|
return vmDiskPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmDiskPaths(List<String> vmDiskPaths) {
|
||||||
|
this.vmDiskPaths = vmDiskPaths;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.to.DataTO;
|
||||||
|
|
||||||
|
public class ReconcileMigrateVolumeCommand extends ReconcileCommand {
|
||||||
|
|
||||||
|
DataTO srcData;
|
||||||
|
DataTO destData;
|
||||||
|
String vmName;
|
||||||
|
|
||||||
|
public ReconcileMigrateVolumeCommand(DataTO srcData, DataTO destData) {
|
||||||
|
this.srcData = srcData;
|
||||||
|
this.destData = destData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTO getSrcData() {
|
||||||
|
return srcData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTO getDestData() {
|
||||||
|
return destData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVmName(String vmName) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
|
||||||
|
|
||||||
|
public class ReconcileVolumeAnswer extends ReconcileAnswer {
|
||||||
|
|
||||||
|
// (1) null object: volume is not available. For example the source is secondary storage
|
||||||
|
// (2) otherwise, if volumeOnSource.getPath() is null, the volume cannot be found on primary storage pool
|
||||||
|
VolumeOnStorageTO volumeOnSource;
|
||||||
|
VolumeOnStorageTO volumeOnDestination;
|
||||||
|
|
||||||
|
public ReconcileVolumeAnswer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReconcileVolumeAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) {
|
||||||
|
this.volumeOnSource = volumeOnSource;
|
||||||
|
this.volumeOnDestination = volumeOnDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeOnStorageTO getVolumeOnSource() {
|
||||||
|
return volumeOnSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeOnStorageTO getVolumeOnDestination() {
|
||||||
|
return volumeOnDestination;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -93,4 +93,9 @@ public class CopyCommand extends StorageSubSystemCommand {
|
|||||||
public void setExecuteInSequence(final boolean inSeq) {
|
public void setExecuteInSequence(final boolean inSeq) {
|
||||||
executeInSequence = inSeq;
|
executeInSequence = inSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReconcile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,8 +25,6 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import com.cloud.agent.api.CheckGuestOsMappingCommand;
|
import com.cloud.agent.api.CheckGuestOsMappingCommand;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.cloud.agent.api.AgentControlCommand;
|
|
||||||
|
|
||||||
public class CheckGuestOsMappingCommandTest {
|
public class CheckGuestOsMappingCommandTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.agent.api.MigrateCommand;
|
||||||
|
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ReconcileCommandUtilsTest {
|
||||||
|
|
||||||
|
final static String COMMANDS_LOG_PATH = "/tmp";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createAndReadAndDeleteLogFilesForCommand() {
|
||||||
|
final String vmName = "TestVM";
|
||||||
|
final String destIp = "DestinationHost";
|
||||||
|
final VirtualMachineTO vmTO = Mockito.mock(VirtualMachineTO.class);
|
||||||
|
final MigrateCommand command = new MigrateCommand(vmName, destIp, false, vmTO, false);
|
||||||
|
long requestSequence = 10000L;
|
||||||
|
command.setRequestSequence(requestSequence);
|
||||||
|
|
||||||
|
String logFile = ReconcileCommandUtils.getLogFileNameForCommand(COMMANDS_LOG_PATH, command);
|
||||||
|
|
||||||
|
ReconcileCommandUtils.createLogFileForCommand(COMMANDS_LOG_PATH, command);
|
||||||
|
Assert.assertTrue((new File(logFile).exists()));
|
||||||
|
|
||||||
|
CommandInfo commandInfo = ReconcileCommandUtils.readLogFileForCommand(logFile);
|
||||||
|
Assert.assertNotNull(commandInfo);
|
||||||
|
Assert.assertEquals(command.getClass().getName(), commandInfo.getCommandName());
|
||||||
|
Assert.assertEquals(Command.State.CREATED, commandInfo.getState());
|
||||||
|
|
||||||
|
Command parseCommand = ReconcileCommandUtils.parseCommandInfo(commandInfo);
|
||||||
|
System.out.println("command state is " + commandInfo);
|
||||||
|
Assert.assertNotNull(parseCommand);
|
||||||
|
Assert.assertTrue(parseCommand instanceof MigrateCommand);
|
||||||
|
Assert.assertEquals(vmName,((MigrateCommand) parseCommand).getVmName());
|
||||||
|
Assert.assertEquals(destIp,((MigrateCommand) parseCommand).getDestinationIp());
|
||||||
|
|
||||||
|
ReconcileCommandUtils.updateLogFileForCommand(COMMANDS_LOG_PATH, command, Command.State.PROCESSING);
|
||||||
|
CommandInfo newCommandInfo = ReconcileCommandUtils.readLogFileForCommand(logFile);
|
||||||
|
System.out.println("new command state is " + newCommandInfo);
|
||||||
|
Assert.assertNotNull(newCommandInfo);
|
||||||
|
Assert.assertEquals(command.getClass().getName(), newCommandInfo.getCommandName());
|
||||||
|
Assert.assertEquals(Command.State.PROCESSING, newCommandInfo.getState());
|
||||||
|
|
||||||
|
ReconcileCommandUtils.deleteLogFileForCommand(COMMANDS_LOG_PATH, command);
|
||||||
|
Assert.assertFalse((new File(logFile).exists()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,7 +32,11 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.cloud.agent.api.CleanupPersistentNetworkResourceCommand;
|
import com.cloud.agent.api.CleanupPersistentNetworkResourceCommand;
|
||||||
|
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
import org.apache.cloudstack.agent.lb.SetupMSListCommand;
|
import org.apache.cloudstack.agent.lb.SetupMSListCommand;
|
||||||
|
import org.apache.cloudstack.command.ReconcileAnswer;
|
||||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -114,6 +118,7 @@ public abstract class AgentAttache {
|
|||||||
protected final long _id;
|
protected final long _id;
|
||||||
protected String _uuid;
|
protected String _uuid;
|
||||||
protected String _name = null;
|
protected String _name = null;
|
||||||
|
protected HypervisorType _hypervisorType;
|
||||||
protected final ConcurrentHashMap<Long, Listener> _waitForList;
|
protected final ConcurrentHashMap<Long, Listener> _waitForList;
|
||||||
protected final LinkedList<Request> _requests;
|
protected final LinkedList<Request> _requests;
|
||||||
protected Long _currentSequence;
|
protected Long _currentSequence;
|
||||||
@ -135,10 +140,11 @@ public abstract class AgentAttache {
|
|||||||
Arrays.sort(s_commandsNotAllowedInConnectingMode);
|
Arrays.sort(s_commandsNotAllowedInConnectingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final boolean maintenance) {
|
protected AgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final HypervisorType hypervisorType, final boolean maintenance) {
|
||||||
_id = id;
|
_id = id;
|
||||||
_uuid = uuid;
|
_uuid = uuid;
|
||||||
_name = name;
|
_name = name;
|
||||||
|
_hypervisorType = hypervisorType;
|
||||||
_waitForList = new ConcurrentHashMap<Long, Listener>();
|
_waitForList = new ConcurrentHashMap<Long, Listener>();
|
||||||
_currentSequence = null;
|
_currentSequence = null;
|
||||||
_maintenance = maintenance;
|
_maintenance = maintenance;
|
||||||
@ -261,6 +267,10 @@ public abstract class AgentAttache {
|
|||||||
return _name;
|
return _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HypervisorType get_hypervisorType() {
|
||||||
|
return _hypervisorType;
|
||||||
|
}
|
||||||
|
|
||||||
public int getQueueSize() {
|
public int getQueueSize() {
|
||||||
return _requests.size();
|
return _requests.size();
|
||||||
}
|
}
|
||||||
@ -406,11 +416,18 @@ public abstract class AgentAttache {
|
|||||||
try {
|
try {
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
Answer[] answers = null;
|
Answer[] answers = null;
|
||||||
|
Command[] cmds = req.getCommands();
|
||||||
|
if (cmds != null && cmds.length == 1 && (cmds[0] != null) && cmds[0].isReconcile()
|
||||||
|
&& !sl.isDisconnected() && _agentMgr.isReconcileCommandsEnabled(_hypervisorType)) {
|
||||||
|
// only available if (1) the only command is a Reconcile command (2) agent is connected; (3) reconciliation is enabled; (4) hypervisor is KVM;
|
||||||
|
answers = waitForAnswerOfReconcileCommand(sl, seq, cmds[0], wait);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
answers = sl.waitFor(wait);
|
answers = sl.waitFor(wait);
|
||||||
} catch (final InterruptedException e) {
|
} catch (final InterruptedException e) {
|
||||||
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted");
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (answers != null) {
|
if (answers != null) {
|
||||||
new Response(req, answers).logD("Received: ", false);
|
new Response(req, answers).logD("Received: ", false);
|
||||||
return answers;
|
return answers;
|
||||||
@ -428,11 +445,13 @@ public abstract class AgentAttache {
|
|||||||
if (current != null && seq != current) {
|
if (current != null && seq != current) {
|
||||||
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waited too long.");
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waited too long.");
|
||||||
|
|
||||||
|
_agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT);
|
||||||
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false);
|
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false);
|
||||||
}
|
}
|
||||||
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waiting some more time because this is the current command");
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waiting some more time because this is the current command");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT);
|
||||||
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait * 2, true);
|
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait * 2, true);
|
||||||
} catch (OperationTimedoutException e) {
|
} catch (OperationTimedoutException e) {
|
||||||
logger.warn(LOG_SEQ_FORMATTED_STRING, seq, "Timed out on " + req.toString());
|
logger.warn(LOG_SEQ_FORMATTED_STRING, seq, "Timed out on " + req.toString());
|
||||||
@ -449,12 +468,57 @@ public abstract class AgentAttache {
|
|||||||
if (req.executeInSequence() && (current != null && current == seq)) {
|
if (req.executeInSequence() && (current != null && current == seq)) {
|
||||||
sendNext(seq);
|
sendNext(seq);
|
||||||
}
|
}
|
||||||
|
_agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT);
|
||||||
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false);
|
throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false);
|
||||||
} finally {
|
} finally {
|
||||||
unregisterListener(seq);
|
unregisterListener(seq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Answer[] waitForAnswerOfReconcileCommand(SynchronousListener sl, final long seq, final Command command, final int wait) {
|
||||||
|
Answer[] answers = null;
|
||||||
|
int waitTimeLeft = wait;
|
||||||
|
while (waitTimeLeft > 0) {
|
||||||
|
int waitTime = Math.min(waitTimeLeft, _agentMgr.getReconcileInterval());
|
||||||
|
logger.debug(String.format("Waiting %s seconds for the answer of reconcile command %s-%s", waitTime, seq, command));
|
||||||
|
if (sl.isDisconnected()) {
|
||||||
|
logger.debug(String.format("Disconnected while waiting for the answer of reconcile command %s-%s", seq, command));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
answers = sl.waitFor(waitTime);
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted");
|
||||||
|
}
|
||||||
|
if (answers != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(String.format("Getting the answer of reconcile command from cloudstack database for %s-%s", seq, command));
|
||||||
|
Pair<Command.State, Answer> commandInfo = _agentMgr.getStateAndAnswerOfReconcileCommand(seq, command);
|
||||||
|
if (commandInfo == null) {
|
||||||
|
logger.debug(String.format("Cannot get the answer of reconcile command from cloudstack database for %s-%s", seq, command));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Command.State state = commandInfo.first();
|
||||||
|
if (Command.State.INTERRUPTED.equals(state)) {
|
||||||
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted by agent, will reconcile it");
|
||||||
|
throw new CloudRuntimeException("Interrupted by agent");
|
||||||
|
} else if (Command.State.DANGLED_IN_BACKEND.equals(state)) {
|
||||||
|
logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Dangling in backend, it seems the agent was restarted, will reconcile it");
|
||||||
|
throw new CloudRuntimeException("It is not being processed by agent");
|
||||||
|
}
|
||||||
|
Answer answer = commandInfo.second();
|
||||||
|
logger.debug(String.format("Got the answer of reconcile command from cloudstack database for %s-%s: %s", seq, command, answer));
|
||||||
|
if (answer != null && !(answer instanceof ReconcileAnswer)) {
|
||||||
|
answers = new Answer[] { answer };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waitTimeLeft -= waitTime;
|
||||||
|
}
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
protected synchronized void sendNext(final long seq) {
|
protected synchronized void sendNext(final long seq) {
|
||||||
_currentSequence = null;
|
_currentSequence = null;
|
||||||
if (_requests.isEmpty()) {
|
if (_requests.isEmpty()) {
|
||||||
|
|||||||
@ -43,6 +43,10 @@ import javax.naming.ConfigurationException;
|
|||||||
|
|
||||||
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
|
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
|
||||||
import org.apache.cloudstack.ca.CAManager;
|
import org.apache.cloudstack.ca.CAManager;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandService;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandUtils;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandVO;
|
||||||
|
import org.apache.cloudstack.command.dao.ReconcileCommandDao;
|
||||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.config.Configurable;
|
import org.apache.cloudstack.framework.config.Configurable;
|
||||||
@ -177,6 +181,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
protected HighAvailabilityManager _haMgr = null;
|
protected HighAvailabilityManager _haMgr = null;
|
||||||
@Inject
|
@Inject
|
||||||
protected AlertManager _alertMgr = null;
|
protected AlertManager _alertMgr = null;
|
||||||
|
@Inject
|
||||||
|
protected ReconcileCommandService reconcileCommandService;
|
||||||
|
@Inject
|
||||||
|
ReconcileCommandDao reconcileCommandDao;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected HypervisorGuruManager _hvGuruMgr;
|
protected HypervisorGuruManager _hvGuruMgr;
|
||||||
@ -207,6 +215,9 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
private final ConcurrentHashMap<String, Long> newAgentConnections = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Long> newAgentConnections = new ConcurrentHashMap<>();
|
||||||
protected ScheduledExecutorService newAgentConnectionsMonitor;
|
protected ScheduledExecutorService newAgentConnectionsMonitor;
|
||||||
|
|
||||||
|
private boolean _reconcileCommandsEnabled = false;
|
||||||
|
private Integer _reconcileCommandInterval;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ResourceManager _resourceMgr;
|
ResourceManager _resourceMgr;
|
||||||
@Inject
|
@Inject
|
||||||
@ -275,6 +286,9 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
|
|
||||||
initializeCommandTimeouts();
|
initializeCommandTimeouts();
|
||||||
|
|
||||||
|
_reconcileCommandsEnabled = ReconcileCommandService.ReconcileCommandsEnabled.value();
|
||||||
|
_reconcileCommandInterval = ReconcileCommandService.ReconcileCommandsInterval.value();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +657,13 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
|
|
||||||
final Request req = new Request(hostId, agent.getName(), _nodeId, cmds, commands.stopOnError(), true);
|
final Request req = new Request(hostId, agent.getName(), _nodeId, cmds, commands.stopOnError(), true);
|
||||||
req.setSequence(agent.getNextSequence());
|
req.setSequence(agent.getNextSequence());
|
||||||
|
|
||||||
|
reconcileCommandService.persistReconcileCommands(hostId, req.getSequence(), cmds);
|
||||||
|
|
||||||
final Answer[] answers = agent.send(req, wait);
|
final Answer[] answers = agent.send(req, wait);
|
||||||
|
|
||||||
|
reconcileCommandService.processAnswers(req.getSequence(), cmds, answers);
|
||||||
|
|
||||||
notifyAnswersToMonitors(hostId, req.getSequence(), answers);
|
notifyAnswersToMonitors(hostId, req.getSequence(), answers);
|
||||||
commands.setAnswers(answers);
|
commands.setAnswers(answers);
|
||||||
return answers;
|
return answers;
|
||||||
@ -940,7 +960,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
|
|
||||||
protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) {
|
protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) {
|
||||||
logger.debug("create DirectAgentAttache for {}", host);
|
logger.debug("create DirectAgentAttache for {}", host);
|
||||||
final DirectAgentAttache attache = new DirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), resource, host.isInMaintenanceStates());
|
final DirectAgentAttache attache = new DirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), resource, host.isInMaintenanceStates());
|
||||||
|
|
||||||
AgentAttache old;
|
AgentAttache old;
|
||||||
synchronized (_agents) {
|
synchronized (_agents) {
|
||||||
@ -1123,6 +1143,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
host = _hostDao.findById(hostId); // Maybe the host magically reappeared?
|
host = _hostDao.findById(hostId); // Maybe the host magically reappeared?
|
||||||
if (host != null && host.getStatus() == Status.Down) {
|
if (host != null && host.getStatus() == Status.Down) {
|
||||||
_haMgr.scheduleRestartForVmsOnHost(host, true, HighAvailabilityManager.ReasonType.HostDown);
|
_haMgr.scheduleRestartForVmsOnHost(host, true, HighAvailabilityManager.ReasonType.HostDown);
|
||||||
|
reconcileCommandService.updateReconcileCommandToInterruptedByHostId(hostId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1285,7 +1306,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
|
|
||||||
protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) {
|
protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) {
|
||||||
logger.debug("create ConnectedAgentAttache for {}", host);
|
logger.debug("create ConnectedAgentAttache for {}", host);
|
||||||
final AgentAttache attache = new ConnectedAgentAttache(this, host.getId(), host.getUuid(), host.getName(), link, host.isInMaintenanceStates());
|
final AgentAttache attache = new ConnectedAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), link, host.isInMaintenanceStates());
|
||||||
link.attach(attache);
|
link.attach(attache);
|
||||||
|
|
||||||
AgentAttache old;
|
AgentAttache old;
|
||||||
@ -1629,6 +1650,9 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
}
|
}
|
||||||
final List<String> avoidMsList = _mshostDao.listNonUpStateMsIPs();
|
final List<String> avoidMsList = _mshostDao.listNonUpStateMsIPs();
|
||||||
answer = new PingAnswer((PingCommand)cmd, avoidMsList, requestStartupCommand);
|
answer = new PingAnswer((PingCommand)cmd, avoidMsList, requestStartupCommand);
|
||||||
|
|
||||||
|
// Add or update reconcile tasks
|
||||||
|
reconcileCommandService.processCommand(cmd, answer);
|
||||||
} else if (cmd instanceof ReadyAnswer) {
|
} else if (cmd instanceof ReadyAnswer) {
|
||||||
final HostVO host = _hostDao.findById(attache.getId());
|
final HostVO host = _hostDao.findById(attache.getId());
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
@ -2082,6 +2106,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
|
params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
|
||||||
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
|
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
|
||||||
params.put(NetworkOrchestrationService.TUNGSTEN_ENABLED.key(), String.valueOf(NetworkOrchestrationService.TUNGSTEN_ENABLED.valueIn(host.getDataCenterId())));
|
params.put(NetworkOrchestrationService.TUNGSTEN_ENABLED.key(), String.valueOf(NetworkOrchestrationService.TUNGSTEN_ENABLED.valueIn(host.getDataCenterId())));
|
||||||
|
params.put(ReconcileCommandService.ReconcileCommandsEnabled.key(), String.valueOf(_reconcileCommandsEnabled));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SetHostParamsCommand cmds = new SetHostParamsCommand(params);
|
SetHostParamsCommand cmds = new SetHostParamsCommand(params);
|
||||||
@ -2168,4 +2193,36 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
|||||||
private GlobalLock getHostJoinLock(Long hostId) {
|
private GlobalLock getHostJoinLock(Long hostId) {
|
||||||
return GlobalLock.getInternLock(String.format("%s-%s", "Host-Join", hostId));
|
return GlobalLock.getInternLock(String.format("%s-%s", "Host-Join", hostId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isReconcileCommandsEnabled(HypervisorType hypervisorType) {
|
||||||
|
return _reconcileCommandsEnabled && ReconcileCommandService.SupportedHypervisorTypes.contains(hypervisorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateReconcileCommandsIfNeeded(long requestSeq, Command[] commands, Command.State state) {
|
||||||
|
if (!_reconcileCommandsEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Command command: commands) {
|
||||||
|
if (command.isReconcile()) {
|
||||||
|
reconcileCommandService.updateReconcileCommand(requestSeq, command, null, state, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Command.State, Answer> getStateAndAnswerOfReconcileCommand(long requestSeq, Command command) {
|
||||||
|
ReconcileCommandVO reconcileCommandVO = reconcileCommandDao.findCommand(requestSeq, command.toString());
|
||||||
|
if (reconcileCommandVO == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Command.State state = reconcileCommandVO.getStateByAgent();
|
||||||
|
if (reconcileCommandVO.getAnswerName() == null || reconcileCommandVO.getAnswerInfo() == null) {
|
||||||
|
return new Pair<>(state, null);
|
||||||
|
}
|
||||||
|
Answer answer = ReconcileCommandUtils.parseAnswerFromAnswerInfo(reconcileCommandVO.getAnswerName(), reconcileCommandVO.getAnswerInfo());
|
||||||
|
return new Pair<>(state, answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getReconcileInterval() {
|
||||||
|
return _reconcileCommandInterval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import com.cloud.agent.api.Command;
|
|||||||
import com.cloud.agent.transport.Request;
|
import com.cloud.agent.transport.Request;
|
||||||
import com.cloud.exception.AgentUnavailableException;
|
import com.cloud.exception.AgentUnavailableException;
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.utils.nio.Link;
|
import com.cloud.utils.nio.Link;
|
||||||
|
|
||||||
public class ClusteredAgentAttache extends ConnectedAgentAttache implements Routable {
|
public class ClusteredAgentAttache extends ConnectedAgentAttache implements Routable {
|
||||||
@ -44,14 +45,14 @@ public class ClusteredAgentAttache extends ConnectedAgentAttache implements Rout
|
|||||||
s_clusteredAgentMgr = agentMgr;
|
s_clusteredAgentMgr = agentMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name) {
|
public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType) {
|
||||||
super(agentMgr, id, uuid, name, null, false);
|
super(agentMgr, id, uuid, name, hypervisorType, null, false);
|
||||||
_forward = true;
|
_forward = true;
|
||||||
_transferRequests = new LinkedList<Request>();
|
_transferRequests = new LinkedList<Request>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Link link, final boolean maintenance) {
|
public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType, final Link link, final boolean maintenance) {
|
||||||
super(agentMgr, id, uuid, name, link, maintenance);
|
super(agentMgr, id, uuid, name, hypervisorType, link, maintenance);
|
||||||
_forward = link == null;
|
_forward = link == null;
|
||||||
_transferRequests = new LinkedList<Request>();
|
_transferRequests = new LinkedList<Request>();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -264,7 +264,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
|||||||
protected AgentAttache createAttache(final HostVO host) {
|
protected AgentAttache createAttache(final HostVO host) {
|
||||||
logger.debug("create forwarding ClusteredAgentAttache for {}", host);
|
logger.debug("create forwarding ClusteredAgentAttache for {}", host);
|
||||||
long id = host.getId();
|
long id = host.getId();
|
||||||
final AgentAttache attache = new ClusteredAgentAttache(this, id, host.getUuid(), host.getName());
|
final AgentAttache attache = new ClusteredAgentAttache(this, id, host.getUuid(), host.getName(), host.getHypervisorType());
|
||||||
AgentAttache old;
|
AgentAttache old;
|
||||||
synchronized (_agents) {
|
synchronized (_agents) {
|
||||||
old = _agents.get(host.getId());
|
old = _agents.get(host.getId());
|
||||||
@ -280,7 +280,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
|||||||
@Override
|
@Override
|
||||||
protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) {
|
protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) {
|
||||||
logger.debug("create ClusteredAgentAttache for {}", host);
|
logger.debug("create ClusteredAgentAttache for {}", host);
|
||||||
final AgentAttache attache = new ClusteredAgentAttache(this, host.getId(), host.getUuid(), host.getName(), link, host.isInMaintenanceStates());
|
final AgentAttache attache = new ClusteredAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), link, host.isInMaintenanceStates());
|
||||||
link.attach(attache);
|
link.attach(attache);
|
||||||
AgentAttache old;
|
AgentAttache old;
|
||||||
synchronized (_agents) {
|
synchronized (_agents) {
|
||||||
@ -296,7 +296,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
|||||||
@Override
|
@Override
|
||||||
protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) {
|
protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) {
|
||||||
logger.debug("Create ClusteredDirectAgentAttache for {}.", host);
|
logger.debug("Create ClusteredDirectAgentAttache for {}.", host);
|
||||||
final DirectAgentAttache attache = new ClusteredDirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), _nodeId, resource, host.isInMaintenanceStates());
|
final DirectAgentAttache attache = new ClusteredDirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), _nodeId, resource, host.isInMaintenanceStates());
|
||||||
AgentAttache old;
|
AgentAttache old;
|
||||||
synchronized (_agents) {
|
synchronized (_agents) {
|
||||||
old = _agents.get(host.getId());
|
old = _agents.get(host.getId());
|
||||||
|
|||||||
@ -20,14 +20,15 @@ import com.cloud.agent.transport.Request;
|
|||||||
import com.cloud.agent.transport.Response;
|
import com.cloud.agent.transport.Response;
|
||||||
import com.cloud.exception.AgentUnavailableException;
|
import com.cloud.exception.AgentUnavailableException;
|
||||||
import com.cloud.exception.UnsupportedVersionException;
|
import com.cloud.exception.UnsupportedVersionException;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.resource.ServerResource;
|
import com.cloud.resource.ServerResource;
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
|
||||||
public class ClusteredDirectAgentAttache extends DirectAgentAttache implements Routable {
|
public class ClusteredDirectAgentAttache extends DirectAgentAttache implements Routable {
|
||||||
private final long _nodeId;
|
private final long _nodeId;
|
||||||
|
|
||||||
public ClusteredDirectAgentAttache(ClusteredAgentManagerImpl agentMgr, long id, String uuid, String name, long mgmtId, ServerResource resource, boolean maintenance) {
|
public ClusteredDirectAgentAttache(ClusteredAgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, long mgmtId, ServerResource resource, boolean maintenance) {
|
||||||
super(agentMgr, id, uuid, name, resource, maintenance);
|
super(agentMgr, id, uuid, name, hypervisorType, resource, maintenance);
|
||||||
_nodeId = mgmtId;
|
_nodeId = mgmtId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import java.nio.channels.ClosedChannelException;
|
|||||||
import com.cloud.agent.transport.Request;
|
import com.cloud.agent.transport.Request;
|
||||||
import com.cloud.exception.AgentUnavailableException;
|
import com.cloud.exception.AgentUnavailableException;
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.utils.nio.Link;
|
import com.cloud.utils.nio.Link;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,8 +32,8 @@ public class ConnectedAgentAttache extends AgentAttache {
|
|||||||
|
|
||||||
protected Link _link;
|
protected Link _link;
|
||||||
|
|
||||||
public ConnectedAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Link link, final boolean maintenance) {
|
public ConnectedAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType, final Link link, final boolean maintenance) {
|
||||||
super(agentMgr, id, uuid, name, maintenance);
|
super(agentMgr, id, uuid, name, hypervisorType, maintenance);
|
||||||
_link = link;
|
_link = link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import com.cloud.agent.transport.Request;
|
|||||||
import com.cloud.agent.transport.Response;
|
import com.cloud.agent.transport.Response;
|
||||||
import com.cloud.exception.AgentUnavailableException;
|
import com.cloud.exception.AgentUnavailableException;
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.resource.ServerResource;
|
import com.cloud.resource.ServerResource;
|
||||||
import org.apache.logging.log4j.ThreadContext;
|
import org.apache.logging.log4j.ThreadContext;
|
||||||
|
|
||||||
@ -51,8 +52,8 @@ public class DirectAgentAttache extends AgentAttache {
|
|||||||
AtomicInteger _outstandingTaskCount;
|
AtomicInteger _outstandingTaskCount;
|
||||||
AtomicInteger _outstandingCronTaskCount;
|
AtomicInteger _outstandingCronTaskCount;
|
||||||
|
|
||||||
public DirectAgentAttache(AgentManagerImpl agentMgr, long id, String uuid,String name, ServerResource resource, boolean maintenance) {
|
public DirectAgentAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, ServerResource resource, boolean maintenance) {
|
||||||
super(agentMgr, id, uuid, name, maintenance);
|
super(agentMgr, id, uuid, name, hypervisorType, maintenance);
|
||||||
_resource = resource;
|
_resource = resource;
|
||||||
_outstandingTaskCount = new AtomicInteger(0);
|
_outstandingTaskCount = new AtomicInteger(0);
|
||||||
_outstandingCronTaskCount = new AtomicInteger(0);
|
_outstandingCronTaskCount = new AtomicInteger(0);
|
||||||
|
|||||||
@ -19,11 +19,12 @@ package com.cloud.agent.manager;
|
|||||||
import com.cloud.agent.transport.Request;
|
import com.cloud.agent.transport.Request;
|
||||||
import com.cloud.exception.AgentUnavailableException;
|
import com.cloud.exception.AgentUnavailableException;
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
|
|
||||||
public class DummyAttache extends AgentAttache {
|
public class DummyAttache extends AgentAttache {
|
||||||
|
|
||||||
public DummyAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, boolean maintenance) {
|
public DummyAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, boolean maintenance) {
|
||||||
super(agentMgr, id, uuid, name, maintenance);
|
super(agentMgr, id, uuid, name, hypervisorType, maintenance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -2843,12 +2843,30 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||||||
throw new CloudRuntimeException(details);
|
throw new CloudRuntimeException(details);
|
||||||
}
|
}
|
||||||
} catch (final OperationTimedoutException e) {
|
} catch (final OperationTimedoutException e) {
|
||||||
|
boolean success = false;
|
||||||
|
if (HypervisorType.KVM.equals(vm.getHypervisorType())) {
|
||||||
|
try {
|
||||||
|
final Answer answer = _agentMgr.send(vm.getHostId(), new CheckVirtualMachineCommand(vm.getInstanceName()));
|
||||||
|
if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) {
|
||||||
|
final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer) answer;
|
||||||
|
if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) {
|
||||||
|
logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, vm.getHostId()));
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error(String.format("Failed to get state of VM %s on destination host %s: %s", vm, vm.getHostId(), ex.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
if (e.isActive()) {
|
if (e.isActive()) {
|
||||||
logger.warn("Active migration command so scheduling a restart for {}", vm, e);
|
logger.warn("Active migration command so scheduling a restart for {}", vm, e);
|
||||||
_haMgr.scheduleRestart(vm, true);
|
_haMgr.scheduleRestart(vm, true);
|
||||||
}
|
|
||||||
throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId);
|
throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) {
|
if (!changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import com.cloud.exception.ConnectionException;
|
|||||||
import com.cloud.host.HostVO;
|
import com.cloud.host.HostVO;
|
||||||
import com.cloud.host.Status;
|
import com.cloud.host.Status;
|
||||||
import com.cloud.host.dao.HostDao;
|
import com.cloud.host.dao.HostDao;
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -47,7 +48,7 @@ public class AgentManagerImplTest {
|
|||||||
host = new HostVO("some-Uuid");
|
host = new HostVO("some-Uuid");
|
||||||
host.setDataCenterId(1L);
|
host.setDataCenterId(1L);
|
||||||
cmds = new StartupCommand[]{new StartupRoutingCommand()};
|
cmds = new StartupCommand[]{new StartupRoutingCommand()};
|
||||||
attache = new ConnectedAgentAttache(null, 1L, "uuid", "kvm-attache", null, false);
|
attache = new ConnectedAgentAttache(null, 1L, "uuid", "kvm-attache", Hypervisor.HypervisorType.KVM, null, false);
|
||||||
|
|
||||||
hostDao = Mockito.mock(HostDao.class);
|
hostDao = Mockito.mock(HostDao.class);
|
||||||
storagePoolMonitor = Mockito.mock(Listener.class);
|
storagePoolMonitor = Mockito.mock(Listener.class);
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.utils.nio.Link;
|
import com.cloud.utils.nio.Link;
|
||||||
|
|
||||||
public class ConnectedAgentAttacheTest {
|
public class ConnectedAgentAttacheTest {
|
||||||
@ -31,8 +32,8 @@ public class ConnectedAgentAttacheTest {
|
|||||||
|
|
||||||
Link link = mock(Link.class);
|
Link link = mock(Link.class);
|
||||||
|
|
||||||
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false);
|
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link, false);
|
||||||
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false);
|
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM,link, false);
|
||||||
|
|
||||||
assertTrue(agentAttache1.equals(agentAttache2));
|
assertTrue(agentAttache1.equals(agentAttache2));
|
||||||
}
|
}
|
||||||
@ -42,7 +43,7 @@ public class ConnectedAgentAttacheTest {
|
|||||||
|
|
||||||
Link link = mock(Link.class);
|
Link link = mock(Link.class);
|
||||||
|
|
||||||
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false);
|
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link, false);
|
||||||
|
|
||||||
assertFalse(agentAttache1.equals(null));
|
assertFalse(agentAttache1.equals(null));
|
||||||
}
|
}
|
||||||
@ -53,8 +54,8 @@ public class ConnectedAgentAttacheTest {
|
|||||||
Link link1 = mock(Link.class);
|
Link link1 = mock(Link.class);
|
||||||
Link link2 = mock(Link.class);
|
Link link2 = mock(Link.class);
|
||||||
|
|
||||||
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link1, false);
|
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false);
|
||||||
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, link2, false);
|
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link2, false);
|
||||||
|
|
||||||
assertFalse(agentAttache1.equals(agentAttache2));
|
assertFalse(agentAttache1.equals(agentAttache2));
|
||||||
}
|
}
|
||||||
@ -64,8 +65,8 @@ public class ConnectedAgentAttacheTest {
|
|||||||
|
|
||||||
Link link1 = mock(Link.class);
|
Link link1 = mock(Link.class);
|
||||||
|
|
||||||
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, link1, false);
|
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false);
|
||||||
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 2, "uuid", null, link1, false);
|
ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 2, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false);
|
||||||
|
|
||||||
assertFalse(agentAttache1.equals(agentAttache2));
|
assertFalse(agentAttache1.equals(agentAttache2));
|
||||||
}
|
}
|
||||||
@ -75,7 +76,7 @@ public class ConnectedAgentAttacheTest {
|
|||||||
|
|
||||||
Link link1 = mock(Link.class);
|
Link link1 = mock(Link.class);
|
||||||
|
|
||||||
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, link1, false);
|
ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false);
|
||||||
|
|
||||||
assertFalse(agentAttache1.equals("abc"));
|
assertFalse(agentAttache1.equals("abc"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import org.mockito.Mockito;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.resource.ServerResource;
|
import com.cloud.resource.ServerResource;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -42,7 +43,7 @@ public class DirectAgentAttacheTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
directAgentAttache = new DirectAgentAttache(_agentMgr, _id, _uuid, "myDirectAgentAttache", _resource, false);
|
directAgentAttache = new DirectAgentAttache(_agentMgr, _id, _uuid, "myDirectAgentAttache", Hypervisor.HypervisorType.KVM, _resource, false);
|
||||||
|
|
||||||
MockitoAnnotations.initMocks(directAgentAttache);
|
MockitoAnnotations.initMocks(directAgentAttache);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,4 +162,6 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
|
|||||||
List<VolumeVO> searchRemovedByVms(List<Long> vmIds, Long batchSize);
|
List<VolumeVO> searchRemovedByVms(List<Long> vmIds, Long batchSize);
|
||||||
|
|
||||||
VolumeVO findOneByIScsiName(String iScsiName);
|
VolumeVO findOneByIScsiName(String iScsiName);
|
||||||
|
|
||||||
|
VolumeVO findByLastIdAndState(long lastVolumeId, Volume.State...states);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ import com.cloud.utils.db.DB;
|
|||||||
import com.cloud.utils.db.Filter;
|
import com.cloud.utils.db.Filter;
|
||||||
import com.cloud.utils.db.GenericDaoBase;
|
import com.cloud.utils.db.GenericDaoBase;
|
||||||
import com.cloud.utils.db.GenericSearchBuilder;
|
import com.cloud.utils.db.GenericSearchBuilder;
|
||||||
|
import com.cloud.utils.db.QueryBuilder;
|
||||||
import com.cloud.utils.db.SearchBuilder;
|
import com.cloud.utils.db.SearchBuilder;
|
||||||
import com.cloud.utils.db.SearchCriteria;
|
import com.cloud.utils.db.SearchCriteria;
|
||||||
import com.cloud.utils.db.SearchCriteria.Func;
|
import com.cloud.utils.db.SearchCriteria.Func;
|
||||||
@ -397,6 +398,7 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
|||||||
AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), Op.EQ);
|
AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), Op.EQ);
|
||||||
AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ);
|
AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ);
|
||||||
AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ);
|
AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ);
|
||||||
|
AllFieldsSearch.and("path", AllFieldsSearch.entity().getPath(), Op.EQ);
|
||||||
AllFieldsSearch.done();
|
AllFieldsSearch.done();
|
||||||
|
|
||||||
RootDiskStateSearch = createSearchBuilder();
|
RootDiskStateSearch = createSearchBuilder();
|
||||||
@ -904,9 +906,18 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
|
|||||||
return searchIncludingRemoved(sc, filter, null, false);
|
return searchIncludingRemoved(sc, filter, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public VolumeVO findOneByIScsiName(String iScsiName) {
|
public VolumeVO findOneByIScsiName(String iScsiName) {
|
||||||
SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();
|
SearchCriteria<VolumeVO> sc = AllFieldsSearch.create();
|
||||||
sc.setParameters("iScsiName", iScsiName);
|
sc.setParameters("iScsiName", iScsiName);
|
||||||
return findOneIncludingRemovedBy(sc);
|
return findOneIncludingRemovedBy(sc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VolumeVO findByLastIdAndState(long lastVolumeId, State ...states) {
|
||||||
|
QueryBuilder<VolumeVO> sc = QueryBuilder.create(VolumeVO.class);
|
||||||
|
sc.and(sc.entity().getLastId(), SearchCriteria.Op.EQ, lastVolumeId);
|
||||||
|
sc.and(sc.entity().getState(), SearchCriteria.Op.IN, (Object[]) states);
|
||||||
|
return sc.find();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,216 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Temporal;
|
||||||
|
import javax.persistence.TemporalType;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.utils.db.GenericDao;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.acl.InfrastructureEntity;
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
|
import org.apache.cloudstack.api.InternalIdentity;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "reconcile_commands")
|
||||||
|
public class ReconcileCommandVO implements InfrastructureEntity, InternalIdentity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@Column(name = "management_server_id")
|
||||||
|
private long managementServerId;
|
||||||
|
|
||||||
|
@Column(name = "host_id")
|
||||||
|
private long hostId;
|
||||||
|
|
||||||
|
@Column(name = "request_sequence")
|
||||||
|
private long requestSequence;
|
||||||
|
|
||||||
|
@Column(name = "resource_id")
|
||||||
|
private long resourceId;
|
||||||
|
|
||||||
|
@Column(name = "resource_type")
|
||||||
|
private ApiCommandResourceType resourceType;
|
||||||
|
|
||||||
|
@Column(name = "state_by_management")
|
||||||
|
private Command.State stateByManagement;
|
||||||
|
|
||||||
|
@Column(name = "state_by_agent")
|
||||||
|
private Command.State stateByAgent;
|
||||||
|
|
||||||
|
@Column(name = "command_name")
|
||||||
|
private String commandName;
|
||||||
|
|
||||||
|
@Column(name = "command_info", length = 65535)
|
||||||
|
private String commandInfo;
|
||||||
|
|
||||||
|
@Column(name = "answer_name")
|
||||||
|
private String answerName;
|
||||||
|
|
||||||
|
@Column(name = "answer_info", length = 65535)
|
||||||
|
private String answerInfo;
|
||||||
|
|
||||||
|
@Column(name = GenericDao.CREATED_COLUMN)
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(name= GenericDao.REMOVED_COLUMN)
|
||||||
|
private Date removed;
|
||||||
|
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
@Column(name= "updated")
|
||||||
|
private Date updated;
|
||||||
|
|
||||||
|
@Column(name= "retry_count")
|
||||||
|
private Long retryCount = 0L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getManagementServerId() {
|
||||||
|
return managementServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManagementServerId(long managementServerId) {
|
||||||
|
this.managementServerId = managementServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHostId() {
|
||||||
|
return hostId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostId(long hostId) {
|
||||||
|
this.hostId = hostId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRequestSequence() {
|
||||||
|
return requestSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestSequence(long requestSequence) {
|
||||||
|
this.requestSequence = requestSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(long resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiCommandResourceType getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(ApiCommandResourceType resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command.State getStateByManagement() {
|
||||||
|
return stateByManagement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateByManagement(Command.State stateByManagement) {
|
||||||
|
this.stateByManagement = stateByManagement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command.State getStateByAgent() {
|
||||||
|
return stateByAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateByAgent(Command.State stateByAgent) {
|
||||||
|
this.stateByAgent = stateByAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommandName() {
|
||||||
|
return commandName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandName(String commandName) {
|
||||||
|
this.commandName = commandName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommandInfo() {
|
||||||
|
return commandInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandInfo(String commandInfo) {
|
||||||
|
this.commandInfo = commandInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswerName() {
|
||||||
|
return answerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswerName(String answerName) {
|
||||||
|
this.answerName = answerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswerInfo() {
|
||||||
|
return answerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswerInfo(String answerInfo) {
|
||||||
|
this.answerInfo = answerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getRemoved() {
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoved(Date removed) {
|
||||||
|
this.removed = removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdated(Date updated) {
|
||||||
|
this.updated = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRetryCount() {
|
||||||
|
return retryCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetryCount(Long retryCount) {
|
||||||
|
this.retryCount = retryCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command.dao;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.utils.db.GenericDao;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ReconcileCommandDao extends GenericDao<ReconcileCommandVO, Long> {
|
||||||
|
|
||||||
|
List<ReconcileCommandVO> listByManagementServerId(long managementServerId);
|
||||||
|
|
||||||
|
List<ReconcileCommandVO> listByHostId(long hostId);
|
||||||
|
|
||||||
|
List<ReconcileCommandVO> listByState(Command.State... states);
|
||||||
|
|
||||||
|
void removeCommand(long commandId, String commandName, Command.State state);
|
||||||
|
|
||||||
|
ReconcileCommandVO findCommand(long reqSequence, String commandName);
|
||||||
|
|
||||||
|
void updateCommandsToInterruptedByManagementServerId(long managementServerId);
|
||||||
|
|
||||||
|
void updateCommandsToInterruptedByHostId(long hostId);
|
||||||
|
|
||||||
|
List<ReconcileCommandVO> listByResourceIdAndTypeAndStates(long resourceId, ApiCommandResourceType resourceType, Command.State... states);
|
||||||
|
}
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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 org.apache.cloudstack.command.dao;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandVO;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
|
import com.cloud.utils.db.DB;
|
||||||
|
import com.cloud.utils.db.GenericDaoBase;
|
||||||
|
import com.cloud.utils.db.QueryBuilder;
|
||||||
|
import com.cloud.utils.db.SearchBuilder;
|
||||||
|
import com.cloud.utils.db.SearchCriteria;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@DB
|
||||||
|
public class ReconcileCommandDaoImpl extends GenericDaoBase<ReconcileCommandVO, Long> implements ReconcileCommandDao {
|
||||||
|
|
||||||
|
final SearchBuilder<ReconcileCommandVO> updateCommandSearch;
|
||||||
|
final SearchBuilder<ReconcileCommandVO> resourceSearch;
|
||||||
|
|
||||||
|
public ReconcileCommandDaoImpl() {
|
||||||
|
|
||||||
|
updateCommandSearch = createSearchBuilder();
|
||||||
|
updateCommandSearch.and("managementServerId", updateCommandSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ);
|
||||||
|
updateCommandSearch.and("stateByManagement", updateCommandSearch.entity().getStateByManagement(), SearchCriteria.Op.IN);
|
||||||
|
updateCommandSearch.and("hostId", updateCommandSearch.entity().getHostId(), SearchCriteria.Op.EQ);
|
||||||
|
updateCommandSearch.and("stateByAgent", updateCommandSearch.entity().getStateByAgent(), SearchCriteria.Op.IN);
|
||||||
|
updateCommandSearch.and("reqSequence", updateCommandSearch.entity().getRequestSequence(), SearchCriteria.Op.EQ);
|
||||||
|
updateCommandSearch.and("commandName", updateCommandSearch.entity().getCommandName(), SearchCriteria.Op.EQ);
|
||||||
|
updateCommandSearch.done();
|
||||||
|
|
||||||
|
resourceSearch = createSearchBuilder();
|
||||||
|
resourceSearch.and("resourceId", resourceSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
|
||||||
|
resourceSearch.and("resourceType", resourceSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||||
|
resourceSearch.and("stateByManagement", resourceSearch.entity().getStateByManagement(), SearchCriteria.Op.IN);
|
||||||
|
resourceSearch.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ReconcileCommandVO> listByManagementServerId(long managementServerId) {
|
||||||
|
QueryBuilder<ReconcileCommandVO> sc = QueryBuilder.create(ReconcileCommandVO.class);
|
||||||
|
sc.and(sc.entity().getManagementServerId(), SearchCriteria.Op.EQ, managementServerId);
|
||||||
|
return sc.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ReconcileCommandVO> listByHostId(long hostId) {
|
||||||
|
QueryBuilder<ReconcileCommandVO> sc = QueryBuilder.create(ReconcileCommandVO.class);
|
||||||
|
sc.and(sc.entity().getHostId(), SearchCriteria.Op.EQ, hostId);
|
||||||
|
return sc.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ReconcileCommandVO> listByState(Command.State... states) {
|
||||||
|
QueryBuilder<ReconcileCommandVO> sc = QueryBuilder.create(ReconcileCommandVO.class);
|
||||||
|
sc.and(sc.entity().getStateByManagement(), SearchCriteria.Op.IN, (Object[]) states);
|
||||||
|
return sc.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeCommand(long reqSequence, String commandName, Command.State state) {
|
||||||
|
SearchCriteria<ReconcileCommandVO> sc = updateCommandSearch.create();
|
||||||
|
sc.setParameters("reqSequence", reqSequence);
|
||||||
|
sc.setParameters("commandName", commandName);
|
||||||
|
|
||||||
|
ReconcileCommandVO vo = createForUpdate();
|
||||||
|
if (state != null) {
|
||||||
|
vo.setStateByManagement(state);
|
||||||
|
}
|
||||||
|
vo.setRemoved(new Date());
|
||||||
|
update(vo, sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReconcileCommandVO findCommand(long reqSequence, String commandName) {
|
||||||
|
SearchCriteria<ReconcileCommandVO> sc = updateCommandSearch.create();
|
||||||
|
sc.setParameters("reqSequence", reqSequence);
|
||||||
|
sc.setParameters("commandName", commandName);
|
||||||
|
return findOneBy(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCommandsToInterruptedByManagementServerId(long managementServerId) {
|
||||||
|
SearchCriteria<ReconcileCommandVO> sc = updateCommandSearch.create();
|
||||||
|
sc.setParameters("managementServerId", managementServerId);
|
||||||
|
sc.setParameters("stateByManagement", Command.State.CREATED, Command.State.RECONCILING);
|
||||||
|
|
||||||
|
ReconcileCommandVO vo = createForUpdate();
|
||||||
|
vo.setStateByManagement(Command.State.INTERRUPTED);
|
||||||
|
|
||||||
|
update(vo, sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCommandsToInterruptedByHostId(long hostId) {
|
||||||
|
SearchCriteria<ReconcileCommandVO> sc = updateCommandSearch.create();
|
||||||
|
sc.setParameters("hostId", hostId);
|
||||||
|
sc.setParameters("stateByAgent", Command.State.STARTED, Command.State.PROCESSING, Command.State.PROCESSING_IN_BACKEND);
|
||||||
|
|
||||||
|
ReconcileCommandVO vo = createForUpdate();
|
||||||
|
vo.setStateByAgent(Command.State.INTERRUPTED);
|
||||||
|
|
||||||
|
update(vo, sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ReconcileCommandVO> listByResourceIdAndTypeAndStates(long resourceId, ApiCommandResourceType resourceType, Command.State... states) {
|
||||||
|
QueryBuilder<ReconcileCommandVO> sc = QueryBuilder.create(ReconcileCommandVO.class);
|
||||||
|
sc.and(sc.entity().getResourceId(), SearchCriteria.Op.EQ, resourceId);
|
||||||
|
sc.and(sc.entity().getResourceType(), SearchCriteria.Op.EQ, resourceType);
|
||||||
|
sc.and(sc.entity().getStateByManagement(), SearchCriteria.Op.IN, (Object[]) states);
|
||||||
|
return sc.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -300,4 +300,5 @@
|
|||||||
<bean id="BgpPeerNetworkMapDaoImpl" class="org.apache.cloudstack.network.dao.BgpPeerNetworkMapDaoImpl" />
|
<bean id="BgpPeerNetworkMapDaoImpl" class="org.apache.cloudstack.network.dao.BgpPeerNetworkMapDaoImpl" />
|
||||||
<bean id="SharedFSDaoImpl" class="org.apache.cloudstack.storage.sharedfs.dao.SharedFSDaoImpl" />
|
<bean id="SharedFSDaoImpl" class="org.apache.cloudstack.storage.sharedfs.dao.SharedFSDaoImpl" />
|
||||||
<bean id="SharedFSJoinDaoImpl" class="org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDaoImpl" />
|
<bean id="SharedFSJoinDaoImpl" class="org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDaoImpl" />
|
||||||
|
<bean id="ReconcileCommandDaoImpl" class="org.apache.cloudstack.command.dao.ReconcileCommandDaoImpl" />
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -37,3 +37,26 @@ WHERE rp.rule = 'quotaStatement'
|
|||||||
AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList');
|
AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList');
|
||||||
|
|
||||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'last_mgmt_server_id', 'bigint unsigned DEFAULT NULL COMMENT "last management server this host is connected to" AFTER `mgmt_server_id`');
|
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'last_mgmt_server_id', 'bigint unsigned DEFAULT NULL COMMENT "last management server this host is connected to" AFTER `mgmt_server_id`');
|
||||||
|
|
||||||
|
-- Add table for reconcile commands
|
||||||
|
CREATE TABLE IF NOT EXISTS `cloud`.`reconcile_commands` (
|
||||||
|
`id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT,
|
||||||
|
`management_server_id` bigint unsigned NOT NULL COMMENT 'node id of the management server',
|
||||||
|
`host_id` bigint unsigned NOT NULL COMMENT 'id of the host',
|
||||||
|
`request_sequence` bigint unsigned NOT NULL COMMENT 'sequence of the request',
|
||||||
|
`resource_id` bigint unsigned DEFAULT NULL COMMENT 'id of the resource',
|
||||||
|
`resource_type` varchar(255) COMMENT 'type if the resource',
|
||||||
|
`state_by_management` varchar(255) COMMENT 'state of the command updated by management server',
|
||||||
|
`state_by_agent` varchar(255) COMMENT 'state of the command updated by cloudstack agent',
|
||||||
|
`command_name` varchar(255) COMMENT 'name of the command',
|
||||||
|
`command_info` MEDIUMTEXT COMMENT 'info of the command',
|
||||||
|
`answer_name` varchar(255) COMMENT 'name of the answer',
|
||||||
|
`answer_info` MEDIUMTEXT COMMENT 'info of the answer',
|
||||||
|
`created` datetime COMMENT 'date the reconcile command was created',
|
||||||
|
`removed` datetime COMMENT 'date the reconcile command was removed',
|
||||||
|
`updated` datetime COMMENT 'date the reconcile command was updated',
|
||||||
|
`retry_count` bigint unsigned DEFAULT 0 COMMENT 'The retry count of reconciliation',
|
||||||
|
PRIMARY KEY(`id`),
|
||||||
|
INDEX `i_reconcile_command__host_id`(`host_id`),
|
||||||
|
CONSTRAINT `fk_reconcile_command__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|||||||
@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.CheckVirtualMachineAnswer;
|
||||||
|
import com.cloud.agent.api.CheckVirtualMachineCommand;
|
||||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||||
@ -2011,6 +2013,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
@Override
|
@Override
|
||||||
public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
|
public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
String errMsg = null;
|
String errMsg = null;
|
||||||
|
boolean success = false;
|
||||||
|
Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (srcHost.getHypervisorType() != HypervisorType.KVM) {
|
if (srcHost.getHypervisorType() != HypervisorType.KVM) {
|
||||||
@ -2024,7 +2028,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<MigrateDiskInfo>();
|
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<MigrateDiskInfo>();
|
||||||
|
|
||||||
Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
|
Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
|
||||||
Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
|
|
||||||
|
|
||||||
boolean managedStorageDestination = false;
|
boolean managedStorageDestination = false;
|
||||||
boolean migrateNonSharedInc = false;
|
boolean migrateNonSharedInc = false;
|
||||||
@ -2140,12 +2143,29 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
|
boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value();
|
||||||
migrateCommand.setAutoConvergence(kvmAutoConvergence);
|
migrateCommand.setAutoConvergence(kvmAutoConvergence);
|
||||||
|
|
||||||
MigrateAnswer migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand);
|
MigrateAnswer migrateAnswer = null;
|
||||||
|
try {
|
||||||
boolean success = migrateAnswer != null && migrateAnswer.getResult();
|
migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand);
|
||||||
|
success = migrateAnswer != null && migrateAnswer.getResult();
|
||||||
|
} catch (OperationTimedoutException ex) {
|
||||||
|
if (HypervisorType.KVM.equals(vm.getHypervisorType())) {
|
||||||
|
final Answer answer = agentManager.send(destHost.getId(), new CheckVirtualMachineCommand(vm.getInstanceName()));
|
||||||
|
if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) {
|
||||||
|
final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer)answer;
|
||||||
|
if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) {
|
||||||
|
logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, destHost));
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost);
|
handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
if (migrateAnswer == null) {
|
if (migrateAnswer == null) {
|
||||||
throw new CloudRuntimeException("Unable to get an answer to the migrate command");
|
throw new CloudRuntimeException("Unable to get an answer to the migrate command");
|
||||||
}
|
}
|
||||||
@ -2155,6 +2175,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (AgentUnavailableException | OperationTimedoutException | CloudRuntimeException ex) {
|
} catch (AgentUnavailableException | OperationTimedoutException | CloudRuntimeException ex) {
|
||||||
String volumesAndStorages = volumeDataStoreMap.entrySet().stream().map(entry -> formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(entry)).collect(Collectors.joining(","));
|
String volumesAndStorages = volumeDataStoreMap.entrySet().stream().map(entry -> formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(entry)).collect(Collectors.joining(","));
|
||||||
|
|
||||||
@ -2163,6 +2184,15 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (!success && !srcVolumeInfoToDestVolumeInfo.isEmpty()) {
|
||||||
|
for (VolumeInfo destVolumeInfo : srcVolumeInfoToDestVolumeInfo.values()) {
|
||||||
|
logger.info(String.format("Expunging dest volume [id: %s, state: %s] as part of failed VM migration with volumes command for VM [%s].", destVolumeInfo.getId(), destVolumeInfo.getState(), vmTO.getId()));
|
||||||
|
destVolumeInfo.processEvent(Event.OperationFailed);
|
||||||
|
destVolumeInfo.processEvent(Event.DestroyRequested);
|
||||||
|
_volumeService.expungeVolumeAsync(destVolumeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||||
|
|
||||||
CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
|
CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
|
||||||
@ -2372,6 +2402,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
newVol.setPodId(storagePoolVO.getPodId());
|
newVol.setPodId(storagePoolVO.getPodId());
|
||||||
newVol.setPoolId(storagePoolVO.getId());
|
newVol.setPoolId(storagePoolVO.getId());
|
||||||
newVol.setLastPoolId(lastPoolId);
|
newVol.setLastPoolId(lastPoolId);
|
||||||
|
newVol.setLastId(volume.getId());
|
||||||
|
|
||||||
if (volume.getPassphraseId() != null) {
|
if (volume.getPassphraseId() != null) {
|
||||||
newVol.setPassphraseId(volume.getPassphraseId());
|
newVol.setPassphraseId(volume.getPassphraseId());
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandService;
|
||||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.config.Configurable;
|
import org.apache.cloudstack.framework.config.Configurable;
|
||||||
@ -104,6 +105,9 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
|||||||
|
|
||||||
private StatusAdministrator statusAdministrator;
|
private StatusAdministrator statusAdministrator;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected ReconcileCommandService reconcileCommandService;
|
||||||
|
|
||||||
//
|
//
|
||||||
// pay attention to _mshostId and _msid
|
// pay attention to _mshostId and _msid
|
||||||
// _mshostId is the primary key of management host table
|
// _mshostId is the primary key of management host table
|
||||||
@ -1013,6 +1017,8 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
|||||||
logger.warn("Management node " + host.getId() + " is detected inactive by timestamp and did not send node status to all other nodes");
|
logger.warn("Management node " + host.getId() + " is detected inactive by timestamp and did not send node status to all other nodes");
|
||||||
host.setState(ManagementServerHost.State.Down);
|
host.setState(ManagementServerHost.State.Down);
|
||||||
_mshostDao.update(host.getId(), host);
|
_mshostDao.update(host.getId(), host);
|
||||||
|
|
||||||
|
reconcileCommandService.updateReconcileCommandToInterruptedByManagementServerId(host.getMsid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import javax.naming.ConfigurationException;
|
|||||||
|
|
||||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||||
import org.apache.cloudstack.api.ApiErrorCode;
|
import org.apache.cloudstack.api.ApiErrorCode;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandService;
|
||||||
import org.apache.cloudstack.context.CallContext;
|
import org.apache.cloudstack.context.CallContext;
|
||||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
|
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
|
||||||
@ -71,6 +72,7 @@ import com.cloud.network.dao.NetworkDao;
|
|||||||
import com.cloud.network.dao.NetworkVO;
|
import com.cloud.network.dao.NetworkVO;
|
||||||
import com.cloud.storage.Snapshot;
|
import com.cloud.storage.Snapshot;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
|
import com.cloud.storage.VolumeVO;
|
||||||
import com.cloud.storage.VolumeDetailVO;
|
import com.cloud.storage.VolumeDetailVO;
|
||||||
import com.cloud.storage.dao.SnapshotDao;
|
import com.cloud.storage.dao.SnapshotDao;
|
||||||
import com.cloud.storage.dao.SnapshotDetailsDao;
|
import com.cloud.storage.dao.SnapshotDetailsDao;
|
||||||
@ -167,6 +169,8 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
|
|||||||
private NetworkDao networkDao;
|
private NetworkDao networkDao;
|
||||||
@Inject
|
@Inject
|
||||||
private NetworkOrchestrationService networkOrchestrationService;
|
private NetworkOrchestrationService networkOrchestrationService;
|
||||||
|
@Inject
|
||||||
|
private ReconcileCommandService reconcileCommandService;
|
||||||
|
|
||||||
private volatile long _executionRunNumber = 1;
|
private volatile long _executionRunNumber = 1;
|
||||||
|
|
||||||
@ -1197,6 +1201,23 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (vol.getState().isTransitional()) {
|
if (vol.getState().isTransitional()) {
|
||||||
|
if (Volume.State.Migrating.equals(vol.getState())) {
|
||||||
|
if (ReconcileCommandService.ReconcileCommandsEnabled.value()) {
|
||||||
|
if (reconcileCommandService.isReconcileResourceNeeded(volumeId, ApiCommandResourceType.Volume)) {
|
||||||
|
logger.debug(String.format("Skipping cleaning up Migrating volume: %s, it will be reconciled", vol));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (vol.getInstanceId() != null && reconcileCommandService.isReconcileResourceNeeded(vol.getInstanceId(), ApiCommandResourceType.VirtualMachine)) {
|
||||||
|
logger.debug(String.format("Skipping cleaning up Migrating volume: %s, the vm %s will be reconciled", vol, _vmInstanceDao.findById(vol.getInstanceId())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VolumeVO destVolume = _volsDao.findByLastIdAndState(vol.getId(), Volume.State.Migrating, Volume.State.Creating);
|
||||||
|
if (destVolume != null) {
|
||||||
|
logger.debug(String.format("Found destination volume of Migrating volume %s: %s", vol, destVolume));
|
||||||
|
cleanupVolume(destVolume.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
logger.debug("Cleaning up volume with Id: " + volumeId);
|
logger.debug("Cleaning up volume with Id: " + volumeId);
|
||||||
boolean status = vol.stateTransit(Volume.Event.OperationFailed);
|
boolean status = vol.stateTransit(Volume.Event.OperationFailed);
|
||||||
cleanupFailedVolumesCreatedFromSnapshots(volumeId);
|
cleanupFailedVolumesCreatedFromSnapshots(volumeId);
|
||||||
@ -1213,6 +1234,18 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (vmInstanceVO.getState().isTransitional()) {
|
if (vmInstanceVO.getState().isTransitional()) {
|
||||||
|
if (VirtualMachine.State.Migrating.equals(vmInstanceVO.getState())) {
|
||||||
|
if (ReconcileCommandService.ReconcileCommandsEnabled.value()
|
||||||
|
&& reconcileCommandService.isReconcileResourceNeeded(vmId, ApiCommandResourceType.VirtualMachine)) {
|
||||||
|
logger.debug(String.format("Skipping cleaning up Instance %s, it will be reconciled", vmInstanceVO));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logger.debug("Cleaning up volumes with instance Id: " + vmId);
|
||||||
|
List<VolumeVO> volumes = _volsDao.findByInstance(vmInstanceVO.getId());
|
||||||
|
for (VolumeVO volume : volumes) {
|
||||||
|
cleanupVolume(volume.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
logger.debug("Cleaning up Instance with Id: " + vmId);
|
logger.debug("Cleaning up Instance with Id: " + vmId);
|
||||||
return virtualMachineManager.stateTransitTo(vmInstanceVO, VirtualMachine.Event.OperationFailed, vmInstanceVO.getHostId());
|
return virtualMachineManager.stateTransitTo(vmInstanceVO, VirtualMachine.Event.OperationFailed, vmInstanceVO.getHostId());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import java.util.Properties;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -53,12 +54,16 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
|
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
|
||||||
|
import org.apache.cloudstack.command.CommandInfo;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandService;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommandUtils;
|
||||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||||
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
|
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
|
||||||
import org.apache.cloudstack.storage.configdrive.ConfigDrive;
|
import org.apache.cloudstack.storage.configdrive.ConfigDrive;
|
||||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
|
import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
|
||||||
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
|
||||||
import org.apache.cloudstack.utils.cryptsetup.CryptSetup;
|
import org.apache.cloudstack.utils.cryptsetup.CryptSetup;
|
||||||
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
|
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
|
||||||
@ -110,6 +115,7 @@ import org.xml.sax.SAXException;
|
|||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
import com.cloud.agent.api.Command;
|
import com.cloud.agent.api.Command;
|
||||||
import com.cloud.agent.api.HostVmStateReportEntry;
|
import com.cloud.agent.api.HostVmStateReportEntry;
|
||||||
|
import com.cloud.agent.api.PingAnswer;
|
||||||
import com.cloud.agent.api.PingCommand;
|
import com.cloud.agent.api.PingCommand;
|
||||||
import com.cloud.agent.api.PingRoutingCommand;
|
import com.cloud.agent.api.PingRoutingCommand;
|
||||||
import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
|
import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
|
||||||
@ -144,6 +150,7 @@ import com.cloud.exception.InternalErrorException;
|
|||||||
import com.cloud.host.Host.Type;
|
import com.cloud.host.Host.Type;
|
||||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||||
import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
|
import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.disconnecthook.DisconnectHook;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
|
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef;
|
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef;
|
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef;
|
||||||
@ -213,6 +220,7 @@ import com.cloud.utils.ssh.SshHelper;
|
|||||||
import com.cloud.vm.VirtualMachine;
|
import com.cloud.vm.VirtualMachine;
|
||||||
import com.cloud.vm.VirtualMachine.PowerState;
|
import com.cloud.vm.VirtualMachine.PowerState;
|
||||||
import com.cloud.vm.VmDetailConstants;
|
import com.cloud.vm.VmDetailConstants;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,6 +350,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
public static final int LIBVIRT_CGROUPV2_WEIGHT_MIN = 2;
|
public static final int LIBVIRT_CGROUPV2_WEIGHT_MIN = 2;
|
||||||
public static final int LIBVIRT_CGROUPV2_WEIGHT_MAX = 10000;
|
public static final int LIBVIRT_CGROUPV2_WEIGHT_MAX = 10000;
|
||||||
|
|
||||||
|
public static final String COMMANDS_LOG_PATH = "/usr/share/cloudstack-agent/tmp/commands";
|
||||||
|
|
||||||
private String modifyVlanPath;
|
private String modifyVlanPath;
|
||||||
private String versionStringPath;
|
private String versionStringPath;
|
||||||
private String patchScriptPath;
|
private String patchScriptPath;
|
||||||
@ -512,6 +522,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
|
|
||||||
private boolean isTungstenEnabled = false;
|
private boolean isTungstenEnabled = false;
|
||||||
|
|
||||||
|
private boolean isReconcileCommandsEnabled = false;
|
||||||
|
|
||||||
private static Gson gson = new Gson();
|
private static Gson gson = new Gson();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -555,6 +567,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<DisconnectHook> _disconnectHooks = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExecutionResult executeInVR(final String routerIp, final String script, final String args) {
|
public ExecutionResult executeInVR(final String routerIp, final String script, final String args) {
|
||||||
return executeInVR(routerIp, script, args, timeout);
|
return executeInVR(routerIp, script, args, timeout);
|
||||||
@ -774,6 +788,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
public StorageSubsystemCommandHandler getStorageHandler() {
|
public StorageSubsystemCommandHandler getStorageHandler() {
|
||||||
return storageHandler;
|
return storageHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class KeyValueInterpreter extends OutputInterpreter {
|
private static final class KeyValueInterpreter extends OutputInterpreter {
|
||||||
private final Map<String, String> map = new HashMap<String, String>();
|
private final Map<String, String> map = new HashMap<String, String>();
|
||||||
|
|
||||||
@ -1517,6 +1532,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
isTungstenEnabled = Boolean.parseBoolean(params.get(NetworkOrchestrationService.TUNGSTEN_ENABLED.key()));
|
isTungstenEnabled = Boolean.parseBoolean(params.get(NetworkOrchestrationService.TUNGSTEN_ENABLED.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.get(ReconcileCommandService.ReconcileCommandsEnabled.key()) != null) {
|
||||||
|
isReconcileCommandsEnabled = Boolean.parseBoolean(params.get(ReconcileCommandService.ReconcileCommandsEnabled.key()));
|
||||||
|
}
|
||||||
|
if (isReconcileCommandsEnabled) {
|
||||||
|
File commandsLogPath = new File(COMMANDS_LOG_PATH);
|
||||||
|
if (!commandsLogPath.exists()) {
|
||||||
|
commandsLogPath.mkdirs();
|
||||||
|
}
|
||||||
|
// Update state of reconcile commands
|
||||||
|
getCommandInfosFromLogFiles(true);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1963,6 +1990,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Answer executeRequest(final Command cmd) {
|
public Answer executeRequest(final Command cmd) {
|
||||||
|
if (isReconcileCommandsEnabled) {
|
||||||
|
ReconcileCommandUtils.updateLogFileForCommand(COMMANDS_LOG_PATH, cmd, Command.State.STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
|
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
|
||||||
try {
|
try {
|
||||||
@ -1972,6 +2002,67 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandInfo[] getCommandInfosFromLogFiles(boolean update) {
|
||||||
|
File commandsLogPath = new File(COMMANDS_LOG_PATH);
|
||||||
|
File[] files = commandsLogPath.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
CommandInfo[] commandInfos = new CommandInfo[files.length];
|
||||||
|
int i = 0;
|
||||||
|
for (File file : files) {
|
||||||
|
CommandInfo commandInfo = ReconcileCommandUtils.readLogFileForCommand(file.getAbsolutePath());
|
||||||
|
if (commandInfo == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
if (Command.State.PROCESSING.equals(commandInfo.getState())) {
|
||||||
|
ReconcileCommandUtils.updateLogFileForCommand(file.getAbsolutePath(), Command.State.INTERRUPTED);
|
||||||
|
} else if (Command.State.PROCESSING_IN_BACKEND.equals(commandInfo.getState())) {
|
||||||
|
ReconcileCommandUtils.updateLogFileForCommand(file.getAbsolutePath(), Command.State.DANGLED_IN_BACKEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug(String.format("Adding reconcile command with seq: %s, command: %s, answer: %s", commandInfo.getRequestSeq(), commandInfo.getCommandName(), commandInfo.getAnswer()));
|
||||||
|
commandInfos[i++] = commandInfo;
|
||||||
|
}
|
||||||
|
return commandInfos;
|
||||||
|
}
|
||||||
|
return new CommandInfo[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createOrUpdateLogFileForCommand(Command command, Command.State state) {
|
||||||
|
if (isReconcileCommandsEnabled) {
|
||||||
|
ReconcileCommandUtils.updateLogFileForCommand(COMMANDS_LOG_PATH, command, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createOrUpdateLogFileForCommand(Command command, Answer answer) {
|
||||||
|
if (isReconcileCommandsEnabled) {
|
||||||
|
ReconcileCommandUtils.updateLogFileWithAnswerForCommand(LibvirtComputingResource.COMMANDS_LOG_PATH, command, answer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processPingAnswer(PingAnswer answer) {
|
||||||
|
PingCommand pingCommand = answer.getCommand();
|
||||||
|
List<String> reconcileCommands = answer.getReconcileCommands();
|
||||||
|
CommandInfo[] commandInfos = pingCommand.getCommandInfos();
|
||||||
|
for (CommandInfo commandInfo : commandInfos) {
|
||||||
|
String commandKey = getCommandKey(commandInfo.getRequestSeq(), commandInfo.getCommandName());
|
||||||
|
if (Arrays.asList(Command.State.COMPLETED, Command.State.FAILED, Command.State.INTERRUPTED, Command.State.TIMED_OUT).contains(commandInfo.getState())) {
|
||||||
|
logger.debug(String.format("Removing command %s in %s state as it has been received by the management server", commandKey, commandInfo.getState()));
|
||||||
|
String fileName = String.format("%s/%s-%s.json", COMMANDS_LOG_PATH, commandInfo.getRequestSeq(), commandInfo.getCommandName());
|
||||||
|
ReconcileCommandUtils.deleteLogFile(fileName);
|
||||||
|
} else if (!reconcileCommands.contains(commandKey)) {
|
||||||
|
logger.debug(String.format("Removing command %s in %s state as it cannot be found by the management server", commandKey, commandInfo.getState()));
|
||||||
|
String fileName = String.format("%s/%s-%s.json", COMMANDS_LOG_PATH, commandInfo.getRequestSeq(), commandInfo.getCommandName());
|
||||||
|
ReconcileCommandUtils.deleteLogFile(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommandKey(long requestSeq, String commandName) {
|
||||||
|
return requestSeq + "-" + commandName;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized boolean destroyTunnelNetwork(final String bridge) {
|
public synchronized boolean destroyTunnelNetwork(final String bridge) {
|
||||||
findOrCreateTunnelNetwork(bridge);
|
findOrCreateTunnelNetwork(bridge);
|
||||||
|
|
||||||
@ -2534,7 +2625,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
* Set quota and period tags on 'ctd' when CPU limit use is set
|
* Set quota and period tags on 'ctd' when CPU limit use is set
|
||||||
*/
|
*/
|
||||||
protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) {
|
protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) {
|
||||||
if (vmTO.getLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) {
|
if (vmTO.isLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) {
|
||||||
Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage();
|
Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage();
|
||||||
int period = CpuTuneDef.DEFAULT_PERIOD;
|
int period = CpuTuneDef.DEFAULT_PERIOD;
|
||||||
int quota = (int) (period * cpuQuotaPercentage);
|
int quota = (int) (period * cpuQuotaPercentage);
|
||||||
@ -3712,6 +3803,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
if (healthCheckResult != HealthCheckResult.IGNORE) {
|
if (healthCheckResult != HealthCheckResult.IGNORE) {
|
||||||
pingRoutingCommand.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS);
|
pingRoutingCommand.setHostHealthCheckResult(healthCheckResult == HealthCheckResult.SUCCESS);
|
||||||
}
|
}
|
||||||
|
if (isReconcileCommandsEnabled) {
|
||||||
|
pingRoutingCommand.setCommandInfos(getCommandInfosFromLogFiles(false));
|
||||||
|
}
|
||||||
return pingRoutingCommand;
|
return pingRoutingCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5656,4 +5750,64 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnected() {
|
||||||
|
LOGGER.info("Detected agent disconnect event, running through " + _disconnectHooks.size() + " disconnect hooks");
|
||||||
|
for (DisconnectHook hook : _disconnectHooks) {
|
||||||
|
hook.start();
|
||||||
|
}
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
for (DisconnectHook hook : _disconnectHooks) {
|
||||||
|
try {
|
||||||
|
long elapsed = System.currentTimeMillis() - start;
|
||||||
|
long remaining = hook.getTimeoutMs() - elapsed;
|
||||||
|
long joinWait = remaining > 0 ? remaining : 1;
|
||||||
|
hook.join(joinWait);
|
||||||
|
hook.interrupt();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
LOGGER.warn("Interrupted disconnect hook: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_disconnectHooks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDisconnectHook(DisconnectHook hook) {
|
||||||
|
LOGGER.debug("Adding disconnect hook " + hook);
|
||||||
|
_disconnectHooks.add(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDisconnectHook(DisconnectHook hook) {
|
||||||
|
LOGGER.debug("Removing disconnect hook " + hook);
|
||||||
|
if (_disconnectHooks.contains(hook)) {
|
||||||
|
LOGGER.debug("Removing disconnect hook " + hook);
|
||||||
|
_disconnectHooks.remove(hook);
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("Requested removal of disconnect hook, but hook not found: " + hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeOnStorageTO getVolumeOnStorage(PrimaryDataStoreTO primaryStore, String volumePath) {
|
||||||
|
try {
|
||||||
|
if (primaryStore.isManaged()) {
|
||||||
|
if (!storagePoolManager.connectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), volumePath, primaryStore.getDetails())) {
|
||||||
|
logger.warn(String.format("Failed to connect src volume %s, in storage pool %s", volumePath, primaryStore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final KVMPhysicalDisk srcVolume = storagePoolManager.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), volumePath);
|
||||||
|
if (srcVolume == null) {
|
||||||
|
logger.debug("Failed to get physical disk for volume: " + volumePath);
|
||||||
|
throw new CloudRuntimeException("Failed to get physical disk for volume at path: " + volumePath);
|
||||||
|
}
|
||||||
|
return new VolumeOnStorageTO(HypervisorType.KVM, srcVolume.getName(), srcVolume.getName(), srcVolume.getPath(),
|
||||||
|
srcVolume.getFormat().toString(), srcVolume.getSize(), srcVolume.getVirtualSize());
|
||||||
|
} catch (final CloudRuntimeException e) {
|
||||||
|
logger.debug(String.format("Failed to get volume %s on storage %s: %s", volumePath, primaryStore, e));
|
||||||
|
return new VolumeOnStorageTO();
|
||||||
|
} finally {
|
||||||
|
if (primaryStore.isManaged()) {
|
||||||
|
storagePoolManager.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), volumePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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.cloud.hypervisor.kvm.resource.disconnecthook;
|
||||||
|
|
||||||
|
/**
|
||||||
|
DisconnectHooks can be used to cleanup/cancel long running commands when
|
||||||
|
connection to the management server is interrupted (which results in job
|
||||||
|
failure). Agent CommandWrappers can register a hook with the
|
||||||
|
libvirtComputingResource at the beginning of processing, and
|
||||||
|
libvirtComputingResource will call it upon disconnect. The CommandWrapper can
|
||||||
|
also remove the hook upon completion of the command.
|
||||||
|
|
||||||
|
DisconnectHooks should implement a run() method that is safe to call and will
|
||||||
|
fail cleanly if there is no cleanup to do. Otherwise the CommandWrapper
|
||||||
|
registering/deregistering the hook should account for any race conditions
|
||||||
|
introduced by the ordering of when the command is processed and when the hook
|
||||||
|
is registered/deregistered.
|
||||||
|
|
||||||
|
If a timeout is set, the hook's run() will be interrupted. It will be up to
|
||||||
|
run() to determine what to do with the InterruptedException, but the hook
|
||||||
|
processing will not wait any longer for the hook to complete.
|
||||||
|
|
||||||
|
Avoid doing anything time intensive as DisconnectHooks will delay agent
|
||||||
|
shutdown.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class DisconnectHook extends Thread {
|
||||||
|
// Default timeout is 10 seconds
|
||||||
|
long timeoutMs = 10000;
|
||||||
|
|
||||||
|
public DisconnectHook(String name) {
|
||||||
|
super();
|
||||||
|
this.setName(this.getClass().getName() + "-" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisconnectHook(String name, long timeout) {
|
||||||
|
this(name);
|
||||||
|
this.timeoutMs = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeoutMs(){ return timeoutMs; }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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.cloud.hypervisor.kvm.resource.disconnecthook;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
|
import org.libvirt.Domain;
|
||||||
|
import org.libvirt.LibvirtException;
|
||||||
|
|
||||||
|
public class MigrationCancelHook extends DisconnectHook {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(MigrationCancelHook.class);
|
||||||
|
|
||||||
|
Domain _migratingDomain;
|
||||||
|
String _vmName;
|
||||||
|
|
||||||
|
public MigrationCancelHook(Domain migratingDomain) throws LibvirtException {
|
||||||
|
super(migratingDomain.getName());
|
||||||
|
_migratingDomain = migratingDomain;
|
||||||
|
_vmName = migratingDomain.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOGGER.info("Interrupted migration of " + _vmName);
|
||||||
|
try {
|
||||||
|
if (_migratingDomain.abortJob() == 0) {
|
||||||
|
LOGGER.warn("Aborted migration job for " + _vmName);
|
||||||
|
}
|
||||||
|
} catch (LibvirtException ex) {
|
||||||
|
LOGGER.warn("Failed to abort migration job for " + _vmName, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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.cloud.hypervisor.kvm.resource.disconnecthook;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
|
import org.libvirt.Domain;
|
||||||
|
import org.libvirt.LibvirtException;
|
||||||
|
|
||||||
|
public class VolumeMigrationCancelHook extends DisconnectHook {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(VolumeMigrationCancelHook.class);
|
||||||
|
|
||||||
|
Domain _migratingDomain;
|
||||||
|
String _vmName;
|
||||||
|
String _destDiskLabel;
|
||||||
|
|
||||||
|
public VolumeMigrationCancelHook(Domain migratingDomain, String destDiskLabel) throws LibvirtException {
|
||||||
|
super(migratingDomain.getName());
|
||||||
|
_migratingDomain = migratingDomain;
|
||||||
|
_vmName = migratingDomain.getName();
|
||||||
|
_destDiskLabel = destDiskLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOGGER.info("Interrupted volume migration of " + _vmName);
|
||||||
|
if (_migratingDomain != null && _destDiskLabel != null) {
|
||||||
|
try {
|
||||||
|
_migratingDomain.blockJobAbort(_destDiskLabel, Domain.BlockJobAbortFlags.ASYNC);
|
||||||
|
LOGGER.warn(String.format("Aborted block job for vm %s and volume: %s", _vmName, _destDiskLabel));
|
||||||
|
} catch (LibvirtException ex) {
|
||||||
|
LOGGER.error(String.format("Failed to abort block job for vm %s and volume: %s due to %s", _vmName, _destDiskLabel, ex.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,8 @@
|
|||||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||||
|
|
||||||
import org.libvirt.Connect;
|
import org.libvirt.Connect;
|
||||||
|
import org.libvirt.Domain;
|
||||||
|
import org.libvirt.DomainInfo;
|
||||||
import org.libvirt.LibvirtException;
|
import org.libvirt.LibvirtException;
|
||||||
|
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
@ -45,6 +47,11 @@ public final class LibvirtCheckVirtualMachineCommandWrapper extends CommandWrapp
|
|||||||
vncPort = libvirtComputingResource.getVncPort(conn, command.getVmName());
|
vncPort = libvirtComputingResource.getVncPort(conn, command.getVmName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Domain vm = conn.domainLookupByName(command.getVmName());
|
||||||
|
if (state == PowerState.PowerOn && DomainInfo.DomainState.VIR_DOMAIN_PAUSED.equals(vm.getInfo().state)) {
|
||||||
|
return new CheckVirtualMachineAnswer(command, PowerState.PowerUnknown, vncPort);
|
||||||
|
}
|
||||||
|
|
||||||
return new CheckVirtualMachineAnswer(command, state, vncPort);
|
return new CheckVirtualMachineAnswer(command, state, vncPort);
|
||||||
} catch (final LibvirtException e) {
|
} catch (final LibvirtException e) {
|
||||||
return new CheckVirtualMachineAnswer(command, e.getMessage());
|
return new CheckVirtualMachineAnswer(command, e.getMessage());
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import java.io.File;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
import com.cloud.agent.api.storage.CopyVolumeAnswer;
|
import com.cloud.agent.api.storage.CopyVolumeAnswer;
|
||||||
import com.cloud.agent.api.storage.CopyVolumeCommand;
|
import com.cloud.agent.api.storage.CopyVolumeCommand;
|
||||||
import com.cloud.agent.api.to.DiskTO;
|
import com.cloud.agent.api.to.DiskTO;
|
||||||
@ -92,7 +93,10 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
|
|||||||
secondaryStoragePool.createFolder(volumeDestPath);
|
secondaryStoragePool.createFolder(volumeDestPath);
|
||||||
storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid());
|
storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid());
|
||||||
secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl + volumeDestPath);
|
secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl + volumeDestPath);
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND);
|
||||||
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, secondaryStoragePool, 0);
|
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, secondaryStoragePool, 0);
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED);
|
||||||
|
|
||||||
return new CopyVolumeAnswer(command, true, null, null, volumeName);
|
return new CopyVolumeAnswer(command, true, null, null, volumeName);
|
||||||
} else {
|
} else {
|
||||||
@ -101,11 +105,14 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
|
|||||||
|
|
||||||
final KVMPhysicalDisk volume = secondaryStoragePool.getPhysicalDisk(command.getVolumePath() + ".qcow2");
|
final KVMPhysicalDisk volume = secondaryStoragePool.getPhysicalDisk(command.getVolumePath() + ".qcow2");
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND);
|
||||||
storagePoolMgr.copyPhysicalDisk(volume, volumeName, primaryPool, 0);
|
storagePoolMgr.copyPhysicalDisk(volume, volumeName, primaryPool, 0);
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED);
|
||||||
|
|
||||||
return new CopyVolumeAnswer(command, true, null, null, volumeName);
|
return new CopyVolumeAnswer(command, true, null, null, volumeName);
|
||||||
}
|
}
|
||||||
} catch (final CloudRuntimeException e) {
|
} catch (final CloudRuntimeException e) {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
return new CopyVolumeAnswer(command, false, e.toString(), null, null);
|
return new CopyVolumeAnswer(command, false, e.toString(), null, null);
|
||||||
} finally {
|
} finally {
|
||||||
if (secondaryStoragePool != null) {
|
if (secondaryStoragePool != null) {
|
||||||
@ -150,10 +157,13 @@ public final class LibvirtCopyVolumeCommandWrapper extends CommandWrapper<CopyVo
|
|||||||
|
|
||||||
KVMPhysicalDisk srcPhysicalDisk = storagePoolMgr.getPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
|
KVMPhysicalDisk srcPhysicalDisk = storagePoolMgr.getPhysicalDisk(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid(), srcPath);
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND);
|
||||||
storagePoolMgr.copyPhysicalDisk(srcPhysicalDisk, destVolumeName, secondaryStoragePool, command.getWait() * 1000);
|
storagePoolMgr.copyPhysicalDisk(srcPhysicalDisk, destVolumeName, secondaryStoragePool, command.getWait() * 1000);
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED);
|
||||||
|
|
||||||
return new CopyVolumeAnswer(command, true, null, null, destVolumePath + destVolumeName);
|
return new CopyVolumeAnswer(command, true, null, null, destVolumePath + destVolumeName);
|
||||||
} catch (final CloudRuntimeException e) {
|
} catch (final CloudRuntimeException e) {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
return new CopyVolumeAnswer(command, false, e.toString(), null, null);
|
return new CopyVolumeAnswer(command, false, e.toString(), null, null);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -64,6 +64,7 @@ import org.w3c.dom.NodeList;
|
|||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
import com.cloud.agent.api.MigrateAnswer;
|
import com.cloud.agent.api.MigrateAnswer;
|
||||||
import com.cloud.agent.api.MigrateCommand;
|
import com.cloud.agent.api.MigrateCommand;
|
||||||
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
|
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
|
||||||
@ -73,6 +74,7 @@ import com.cloud.agent.api.to.DpdkTO;
|
|||||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||||
import com.cloud.agent.properties.AgentProperties;
|
import com.cloud.agent.properties.AgentProperties;
|
||||||
import com.cloud.agent.properties.AgentPropertiesFileHandler;
|
import com.cloud.agent.properties.AgentPropertiesFileHandler;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.disconnecthook.MigrationCancelHook;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
|
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
|
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
|
||||||
@ -111,6 +113,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
String result = null;
|
String result = null;
|
||||||
|
Command.State commandState = null;
|
||||||
|
|
||||||
List<InterfaceDef> ifaces = null;
|
List<InterfaceDef> ifaces = null;
|
||||||
List<DiskDef> disks;
|
List<DiskDef> disks;
|
||||||
@ -121,6 +124,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
Connect conn = null;
|
Connect conn = null;
|
||||||
String xmlDesc = null;
|
String xmlDesc = null;
|
||||||
List<Ternary<String, Boolean, String>> vmsnapshots = null;
|
List<Ternary<String, Boolean, String>> vmsnapshots = null;
|
||||||
|
MigrationCancelHook cancelHook = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||||
@ -237,6 +241,12 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
final ExecutorService executor = Executors.newFixedThreadPool(1);
|
||||||
boolean migrateNonSharedInc = command.isMigrateNonSharedInc() && !migrateStorageManaged;
|
boolean migrateNonSharedInc = command.isMigrateNonSharedInc() && !migrateStorageManaged;
|
||||||
|
|
||||||
|
// add cancel hook before we start. If migration fails to start and hook is called, it's non-fatal
|
||||||
|
cancelHook = new MigrationCancelHook(dm);
|
||||||
|
libvirtComputingResource.addDisconnectHook(cancelHook);
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING);
|
||||||
|
|
||||||
final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc,
|
final Callable<Domain> worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc,
|
||||||
migrateStorage, migrateNonSharedInc,
|
migrateStorage, migrateNonSharedInc,
|
||||||
command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels);
|
command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels);
|
||||||
@ -278,6 +288,8 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
logger.info(String.format("Aborting migration of VM [%s] with domain job [%s] due to time out after %d seconds.", vmName, job, migrateWait));
|
logger.info(String.format("Aborting migration of VM [%s] with domain job [%s] due to time out after %d seconds.", vmName, job, migrateWait));
|
||||||
dm.abortJob();
|
dm.abortJob();
|
||||||
result = String.format("Migration of VM [%s] was cancelled by CloudStack due to time out after %d seconds.", vmName, migrateWait);
|
result = String.format("Migration of VM [%s] was cancelled by CloudStack due to time out after %d seconds.", vmName, migrateWait);
|
||||||
|
commandState = Command.State.FAILED;
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, commandState);
|
||||||
logger.debug(result);
|
logger.debug(result);
|
||||||
break;
|
break;
|
||||||
} catch (final LibvirtException e) {
|
} catch (final LibvirtException e) {
|
||||||
@ -338,6 +350,9 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
result = "Exception during migrate: " + e.getMessage();
|
result = "Exception during migrate: " + e.getMessage();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
if (cancelHook != null) {
|
||||||
|
libvirtComputingResource.removeDisconnectHook(cancelHook);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (dm != null && result != null) {
|
if (dm != null && result != null) {
|
||||||
// restore vm snapshots in case of failed migration
|
// restore vm snapshots in case of failed migration
|
||||||
@ -373,6 +388,11 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
|
|||||||
vifDriver.unplug(iface, libvirtComputingResource.shouldDeleteBridge(vlanToPersistenceMap, vlanId));
|
vifDriver.unplug(iface, libvirtComputingResource.shouldDeleteBridge(vlanToPersistenceMap, vlanId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
commandState = Command.State.COMPLETED;
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, commandState);
|
||||||
|
} else if (commandState == null) {
|
||||||
|
commandState = Command.State.FAILED;
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, commandState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MigrateAnswer(command, result == null, result, null);
|
return new MigrateAnswer(command, result == null, result, null);
|
||||||
|
|||||||
@ -20,10 +20,12 @@
|
|||||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||||
|
|
||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
|
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
|
||||||
import com.cloud.agent.api.storage.MigrateVolumeCommand;
|
import com.cloud.agent.api.storage.MigrateVolumeCommand;
|
||||||
import com.cloud.agent.api.to.DiskTO;
|
import com.cloud.agent.api.to.DiskTO;
|
||||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.disconnecthook.VolumeMigrationCancelHook;
|
||||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||||
@ -103,6 +105,8 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
|
|||||||
final String destDiskFileName = ScaleIOUtil.DISK_NAME_PREFIX + destSystemId + "-" + destVolumeId;
|
final String destDiskFileName = ScaleIOUtil.DISK_NAME_PREFIX + destSystemId + "-" + destVolumeId;
|
||||||
final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + destDiskFileName;
|
final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + destDiskFileName;
|
||||||
|
|
||||||
|
VolumeMigrationCancelHook cancelHook = null;
|
||||||
|
|
||||||
Domain dm = null;
|
Domain dm = null;
|
||||||
try {
|
try {
|
||||||
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||||
@ -136,10 +140,23 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
|
|||||||
TypedParameter[] parameters = new TypedParameter[1];
|
TypedParameter[] parameters = new TypedParameter[1];
|
||||||
parameters[0] = parameter;
|
parameters[0] = parameter;
|
||||||
|
|
||||||
|
cancelHook = new VolumeMigrationCancelHook(dm, destDiskLabel);
|
||||||
|
libvirtComputingResource.addDisconnectHook(cancelHook);
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND);
|
||||||
|
|
||||||
dm.blockCopy(destDiskLabel, diskdef, parameters, Domain.BlockCopyFlags.REUSE_EXT);
|
dm.blockCopy(destDiskLabel, diskdef, parameters, Domain.BlockCopyFlags.REUSE_EXT);
|
||||||
logger.info(String.format("Block copy has started for the volume %s : %s ", destDiskLabel, srcPath));
|
logger.info(String.format("Block copy has started for the volume %s : %s ", destDiskLabel, srcPath));
|
||||||
|
|
||||||
return checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, srcSecretUUID);
|
MigrateVolumeAnswer answer = checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, srcSecretUUID);
|
||||||
|
if (answer != null) {
|
||||||
|
if (answer.getResult()) {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED);
|
||||||
|
} else {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String msg = "Migrate volume failed due to " + e.toString();
|
String msg = "Migrate volume failed due to " + e.toString();
|
||||||
logger.warn(msg, e);
|
logger.warn(msg, e);
|
||||||
@ -150,8 +167,12 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
|
|||||||
logger.error("Migrate volume failed while aborting the block job due to " + ex.getMessage());
|
logger.error("Migrate volume failed while aborting the block job due to " + ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
return new MigrateVolumeAnswer(command, false, msg, null);
|
return new MigrateVolumeAnswer(command, false, msg, null);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (cancelHook != null) {
|
||||||
|
libvirtComputingResource.removeDisconnectHook(cancelHook);
|
||||||
|
}
|
||||||
if (dm != null) {
|
if (dm != null) {
|
||||||
try {
|
try {
|
||||||
dm.free();
|
dm.free();
|
||||||
@ -299,6 +320,11 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
|
|||||||
String destPath = destDetails != null && destDetails.get(DiskTO.IQN) != null ? destDetails.get(DiskTO.IQN) :
|
String destPath = destDetails != null && destDetails.get(DiskTO.IQN) != null ? destDetails.get(DiskTO.IQN) :
|
||||||
(destVolumeObjectTO.getPath() != null ? destVolumeObjectTO.getPath() : UUID.randomUUID().toString());
|
(destVolumeObjectTO.getPath() != null ? destVolumeObjectTO.getPath() : UUID.randomUUID().toString());
|
||||||
|
|
||||||
|
// Update path in the command for reconciliation
|
||||||
|
if (destVolumeObjectTO.getPath() == null) {
|
||||||
|
destVolumeObjectTO.setPath(destPath);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KVMStoragePool sourceStoragePool = storagePoolManager.getStoragePool(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid());
|
KVMStoragePool sourceStoragePool = storagePoolManager.getStoragePool(srcPrimaryDataStore.getPoolType(), srcPrimaryDataStore.getUuid());
|
||||||
|
|
||||||
@ -317,12 +343,16 @@ public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVo
|
|||||||
return new MigrateVolumeAnswer(command, false, "Unable to connect destination volume on hypervisor", srcPath);
|
return new MigrateVolumeAnswer(command, false, "Unable to connect destination volume on hypervisor", srcPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.PROCESSING_IN_BACKEND);
|
||||||
KVMPhysicalDisk newDiskCopy = storagePoolManager.copyPhysicalDisk(srcPhysicalDisk, destPath, destPrimaryStorage, command.getWaitInMillSeconds());
|
KVMPhysicalDisk newDiskCopy = storagePoolManager.copyPhysicalDisk(srcPhysicalDisk, destPath, destPrimaryStorage, command.getWaitInMillSeconds());
|
||||||
if (newDiskCopy == null) {
|
if (newDiskCopy == null) {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
return new MigrateVolumeAnswer(command, false, "Copy command failed to return handle to copied physical disk", destPath);
|
return new MigrateVolumeAnswer(command, false, "Copy command failed to return handle to copied physical disk", destPath);
|
||||||
}
|
}
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.COMPLETED);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, Command.State.FAILED);
|
||||||
return new MigrateVolumeAnswer(command, false, ex.getMessage(), null);
|
return new MigrateVolumeAnswer(command, false, ex.getMessage(), null);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
@ -0,0 +1,258 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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.cloud.hypervisor.kvm.resource.wrapper;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Answer;
|
||||||
|
import com.cloud.agent.api.to.DataObjectType;
|
||||||
|
import com.cloud.agent.api.to.DataStoreTO;
|
||||||
|
import com.cloud.agent.api.to.DataTO;
|
||||||
|
import com.cloud.agent.api.to.DiskTO;
|
||||||
|
import com.cloud.agent.api.to.NfsTO;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||||
|
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
|
||||||
|
import com.cloud.resource.CommandWrapper;
|
||||||
|
import com.cloud.resource.ResourceWrapper;
|
||||||
|
import com.cloud.storage.DataStoreRole;
|
||||||
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.command.ReconcileAnswer;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCommand;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCopyAnswer;
|
||||||
|
import org.apache.cloudstack.command.ReconcileCopyCommand;
|
||||||
|
import org.apache.cloudstack.command.ReconcileMigrateAnswer;
|
||||||
|
import org.apache.cloudstack.command.ReconcileMigrateCommand;
|
||||||
|
import org.apache.cloudstack.command.ReconcileMigrateVolumeAnswer;
|
||||||
|
import org.apache.cloudstack.command.ReconcileMigrateVolumeCommand;
|
||||||
|
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||||
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
|
import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
|
||||||
|
import org.libvirt.Connect;
|
||||||
|
import org.libvirt.Domain;
|
||||||
|
import org.libvirt.DomainInfo.DomainState;
|
||||||
|
import org.libvirt.LibvirtException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ResourceWrapper(handles = ReconcileCommand.class)
|
||||||
|
public final class LibvirtReconcileCommandWrapper extends CommandWrapper<ReconcileCommand, Answer, LibvirtComputingResource> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Answer execute(final ReconcileCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
|
||||||
|
if (command instanceof ReconcileMigrateCommand) {
|
||||||
|
return handle((ReconcileMigrateCommand) command, libvirtComputingResource);
|
||||||
|
} else if (command instanceof ReconcileCopyCommand) {
|
||||||
|
return handle((ReconcileCopyCommand) command, libvirtComputingResource);
|
||||||
|
} else if (command instanceof ReconcileMigrateVolumeCommand) {
|
||||||
|
return handle((ReconcileMigrateVolumeCommand) command, libvirtComputingResource);
|
||||||
|
}
|
||||||
|
return new ReconcileAnswer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReconcileAnswer handle(final ReconcileMigrateCommand reconcileCommand, final LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
String vmName = reconcileCommand.getVmName();
|
||||||
|
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||||
|
|
||||||
|
ReconcileMigrateAnswer answer;
|
||||||
|
try {
|
||||||
|
Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
|
||||||
|
Domain vm = conn.domainLookupByName(vmName);
|
||||||
|
DomainState domainState = vm.getInfo().state;
|
||||||
|
logger.debug(String.format("Found VM %s with domain state %s", vmName, domainState));
|
||||||
|
VirtualMachine.State state = getState(domainState);
|
||||||
|
List<String> disks = null;
|
||||||
|
if (VirtualMachine.State.Running.equals(state)) {
|
||||||
|
disks = getVmDiskPaths(libvirtComputingResource.getDisks(conn, vmName));
|
||||||
|
}
|
||||||
|
answer = new ReconcileMigrateAnswer(vmName, state);
|
||||||
|
answer.setVmDisks(disks);
|
||||||
|
} catch (LibvirtException e) {
|
||||||
|
logger.debug(String.format("Failed to get state of VM %s, assume it is Stopped", vmName));
|
||||||
|
VirtualMachine.State state = VirtualMachine.State.Stopped;
|
||||||
|
answer = new ReconcileMigrateAnswer(vmName, state);
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VirtualMachine.State getState(DomainState domainState) {
|
||||||
|
VirtualMachine.State state;
|
||||||
|
if (domainState == DomainState.VIR_DOMAIN_RUNNING) {
|
||||||
|
state = VirtualMachine.State.Running;
|
||||||
|
} else if (Arrays.asList(DomainState.VIR_DOMAIN_SHUTDOWN, DomainState.VIR_DOMAIN_SHUTOFF, DomainState.VIR_DOMAIN_CRASHED).contains(domainState)) {
|
||||||
|
state = VirtualMachine.State.Stopped;
|
||||||
|
} else if (domainState == DomainState.VIR_DOMAIN_PAUSED) {
|
||||||
|
state = VirtualMachine.State.Unknown;
|
||||||
|
} else {
|
||||||
|
state = VirtualMachine.State.Unknown;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getVmDiskPaths(List<LibvirtVMDef.DiskDef> diskDefs) {
|
||||||
|
List<String> diskPaths = new ArrayList<String>();
|
||||||
|
for (LibvirtVMDef.DiskDef diskDef : diskDefs) {
|
||||||
|
if (diskDef.getDiskPath() != null) {
|
||||||
|
diskPaths.add(diskDef.getDiskPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diskPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReconcileAnswer handle(final ReconcileCopyCommand reconcileCommand, final LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
DataTO srcData = reconcileCommand.getSrcData();
|
||||||
|
DataTO destData = reconcileCommand.getDestData();
|
||||||
|
DataStoreTO srcDataStore = srcData.getDataStore();
|
||||||
|
DataStoreTO destDataStore = destData.getDataStore();
|
||||||
|
|
||||||
|
// consistent with StorageSubsystemCommandHandlerBase.execute(CopyCommand cmd)
|
||||||
|
if (srcData.getObjectType() == DataObjectType.TEMPLATE &&
|
||||||
|
(srcData.getDataStore().getRole() == DataStoreRole.Image || srcData.getDataStore().getRole() == DataStoreRole.ImageCache) &&
|
||||||
|
destData.getDataStore().getRole() == DataStoreRole.Primary) {
|
||||||
|
String reason = "copy template to primary storage";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.TEMPLATE && srcDataStore.getRole() == DataStoreRole.Primary &&
|
||||||
|
destDataStore.getRole() == DataStoreRole.Primary) {
|
||||||
|
String reason = "clone template to a volume";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.VOLUME &&
|
||||||
|
(srcData.getDataStore().getRole() == DataStoreRole.ImageCache || srcDataStore.getRole() == DataStoreRole.Image)) {
|
||||||
|
logger.debug("Reconciling: copy volume from image cache to primary");
|
||||||
|
return reconcileCopyVolumeFromImageCacheToPrimary(srcData, destData, reconcileCommand.getOption2(), libvirtComputingResource);
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.VOLUME && srcData.getDataStore().getRole() == DataStoreRole.Primary) {
|
||||||
|
if (destData.getObjectType() == DataObjectType.VOLUME) {
|
||||||
|
if ((srcData instanceof VolumeObjectTO && ((VolumeObjectTO)srcData).isDirectDownload()) ||
|
||||||
|
destData.getDataStore().getRole() == DataStoreRole.Primary) {
|
||||||
|
logger.debug("Reconciling: copy volume from primary to primary");
|
||||||
|
return reconcileCopyVolumeFromPrimaryToPrimary(srcData, destData, libvirtComputingResource);
|
||||||
|
} else {
|
||||||
|
logger.debug("Reconciling: copy volume from primary to secondary");
|
||||||
|
return reconcileCopyVolumeFromPrimaryToSecondary(srcData, destData, reconcileCommand.getOption(), libvirtComputingResource);
|
||||||
|
}
|
||||||
|
} else if (destData.getObjectType() == DataObjectType.TEMPLATE) {
|
||||||
|
String reason = "create volume from template";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
}
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.SNAPSHOT &&
|
||||||
|
srcData.getDataStore().getRole() == DataStoreRole.Primary) {
|
||||||
|
String reason = "backup snapshot from primary";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.VOLUME) {
|
||||||
|
String reason = "create volume from snapshot";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
} else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.TEMPLATE) {
|
||||||
|
String reason = "create template from snapshot";
|
||||||
|
return new ReconcileCopyAnswer(true, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReconcileCopyAnswer(true, "not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReconcileCopyAnswer reconcileCopyVolumeFromImageCacheToPrimary(DataTO srcData, DataTO destData, Map<String, String> details, LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
// consistent with KVMStorageProcessor.copyVolumeFromImageCacheToPrimary
|
||||||
|
final DataStoreTO srcStore = srcData.getDataStore();
|
||||||
|
if (!(srcStore instanceof NfsTO)) {
|
||||||
|
return new ReconcileCopyAnswer(true, "can only handle nfs storage as source");
|
||||||
|
}
|
||||||
|
final DataStoreTO destStore = destData.getDataStore();
|
||||||
|
final PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)destStore;
|
||||||
|
String path = destData.getPath();
|
||||||
|
if (path == null) {
|
||||||
|
path = details != null ? details.get(DiskTO.PATH) : null;
|
||||||
|
}
|
||||||
|
if (path == null) {
|
||||||
|
path = details != null ? details.get(DiskTO.IQN) : null;
|
||||||
|
}
|
||||||
|
if (path == null) {
|
||||||
|
return new ReconcileCopyAnswer(true, "path and iqn on destination storage are null");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
VolumeOnStorageTO volumeOnDestination = libvirtComputingResource.getVolumeOnStorage(primaryStore, path);
|
||||||
|
return new ReconcileCopyAnswer(null, volumeOnDestination);
|
||||||
|
} catch (final CloudRuntimeException e) {
|
||||||
|
logger.debug("Failed to reconcile CopyVolumeFromImageCacheToPrimary: ", e);
|
||||||
|
return new ReconcileCopyAnswer(false, false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ReconcileCopyAnswer reconcileCopyVolumeFromPrimaryToPrimary(DataTO srcData, DataTO destData, LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
// consistent with KVMStorageProcessor.copyVolumeFromPrimaryToPrimary
|
||||||
|
final String srcVolumePath = srcData.getPath();
|
||||||
|
final String destVolumePath = destData.getPath();
|
||||||
|
final DataStoreTO srcStore = srcData.getDataStore();
|
||||||
|
final DataStoreTO destStore = destData.getDataStore();
|
||||||
|
final PrimaryDataStoreTO srcPrimaryStore = (PrimaryDataStoreTO)srcStore;
|
||||||
|
final PrimaryDataStoreTO destPrimaryStore = (PrimaryDataStoreTO)destStore;
|
||||||
|
|
||||||
|
VolumeOnStorageTO volumeOnSource = null;
|
||||||
|
VolumeOnStorageTO volumeOnDestination = null;
|
||||||
|
try {
|
||||||
|
volumeOnSource = libvirtComputingResource.getVolumeOnStorage(srcPrimaryStore, srcVolumePath);
|
||||||
|
if (destPrimaryStore.isManaged() || destVolumePath != null) {
|
||||||
|
volumeOnDestination = libvirtComputingResource.getVolumeOnStorage(destPrimaryStore, destVolumePath);
|
||||||
|
}
|
||||||
|
return new ReconcileCopyAnswer(volumeOnSource, volumeOnDestination);
|
||||||
|
} catch (final CloudRuntimeException e) {
|
||||||
|
logger.debug("Failed to reconcile CopyVolumeFromPrimaryToPrimary: ", e);
|
||||||
|
return new ReconcileCopyAnswer(false, false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReconcileCopyAnswer reconcileCopyVolumeFromPrimaryToSecondary(DataTO srcData, DataTO destData, Map<String, String> details, LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
// consistent with KVMStorageProcessor.copyVolumeFromPrimaryToSecondary
|
||||||
|
final String srcVolumePath = srcData.getPath();
|
||||||
|
final DataStoreTO srcStore = srcData.getDataStore();
|
||||||
|
final DataStoreTO destStore = destData.getDataStore();
|
||||||
|
final PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO)srcStore;
|
||||||
|
if (!(destStore instanceof NfsTO)) {
|
||||||
|
return new ReconcileCopyAnswer(true, "can only handle nfs storage as destination");
|
||||||
|
}
|
||||||
|
VolumeOnStorageTO volumeOnSource = libvirtComputingResource.getVolumeOnStorage(primaryStore, srcVolumePath);
|
||||||
|
return new ReconcileCopyAnswer(volumeOnSource, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReconcileAnswer handle(final ReconcileMigrateVolumeCommand reconcileCommand, final LibvirtComputingResource libvirtComputingResource) {
|
||||||
|
// consistent with LibvirtMigrateVolumeCommandWrapper.execute
|
||||||
|
DataTO srcData = reconcileCommand.getSrcData();
|
||||||
|
DataTO destData = reconcileCommand.getDestData();
|
||||||
|
PrimaryDataStoreTO srcDataStore = (PrimaryDataStoreTO) srcData.getDataStore();
|
||||||
|
PrimaryDataStoreTO destDataStore = (PrimaryDataStoreTO) destData.getDataStore();
|
||||||
|
|
||||||
|
VolumeOnStorageTO volumeOnSource = libvirtComputingResource.getVolumeOnStorage(srcDataStore, srcData.getPath());
|
||||||
|
VolumeOnStorageTO volumeOnDestination = libvirtComputingResource.getVolumeOnStorage(destDataStore, destData.getPath());
|
||||||
|
|
||||||
|
ReconcileMigrateVolumeAnswer answer = new ReconcileMigrateVolumeAnswer(volumeOnSource, volumeOnDestination);
|
||||||
|
String vmName = reconcileCommand.getVmName();
|
||||||
|
if (vmName != null) {
|
||||||
|
try {
|
||||||
|
LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||||
|
Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
|
||||||
|
List<String> disks = getVmDiskPaths(libvirtComputingResource.getDisks(conn, vmName));
|
||||||
|
answer.setVmName(vmName);
|
||||||
|
answer.setVmDiskPaths(disks);
|
||||||
|
} catch (LibvirtException e) {
|
||||||
|
logger.error(String.format("Unable to get disks for %s due to %s", vmName, e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -75,6 +75,13 @@ public class LibvirtRequestWrapper extends RequestWrapper {
|
|||||||
if (commandWrapper == null) {
|
if (commandWrapper == null) {
|
||||||
throw new CommandNotSupported("No way to handle " + command.getClass());
|
throw new CommandNotSupported("No way to handle " + command.getClass());
|
||||||
}
|
}
|
||||||
return commandWrapper.execute(command, serverResource);
|
Answer answer = commandWrapper.execute(command, serverResource);
|
||||||
|
|
||||||
|
if (answer != null && command.isReconcile() && serverResource instanceof LibvirtComputingResource) {
|
||||||
|
LibvirtComputingResource libvirtComputingResource = (LibvirtComputingResource) serverResource;
|
||||||
|
libvirtComputingResource.createOrUpdateLogFileForCommand(command, answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.Command;
|
||||||
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
|
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
|
||||||
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
|
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
|
||||||
import org.apache.cloudstack.direct.download.DirectDownloadHelper;
|
import org.apache.cloudstack.direct.download.DirectDownloadHelper;
|
||||||
@ -341,8 +342,8 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String derivePath(PrimaryDataStoreTO primaryStore, DataTO destData, Map<String, String> details) {
|
public static String derivePath(PrimaryDataStoreTO primaryStore, DataTO destData, Map<String, String> details) {
|
||||||
String path;
|
String path = null;
|
||||||
if (primaryStore.getPoolType() == StoragePoolType.FiberChannel) {
|
if (primaryStore.getPoolType() == StoragePoolType.FiberChannel) {
|
||||||
path = destData.getPath();
|
path = destData.getPath();
|
||||||
} else {
|
} else {
|
||||||
@ -527,6 +528,11 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
|
|
||||||
final String volumeName = UUID.randomUUID().toString();
|
final String volumeName = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
// Update path in the command for reconciliation
|
||||||
|
if (destData.getPath() == null) {
|
||||||
|
((VolumeObjectTO) destData).setPath(volumeName);
|
||||||
|
}
|
||||||
|
|
||||||
final int index = srcVolumePath.lastIndexOf(File.separator);
|
final int index = srcVolumePath.lastIndexOf(File.separator);
|
||||||
final String volumeDir = srcVolumePath.substring(0, index);
|
final String volumeDir = srcVolumePath.substring(0, index);
|
||||||
String srcVolumeName = srcVolumePath.substring(index + 1);
|
String srcVolumeName = srcVolumePath.substring(index + 1);
|
||||||
@ -543,7 +549,9 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
volume.setDispName(srcVol.getName());
|
volume.setDispName(srcVol.getName());
|
||||||
volume.setVmName(srcVol.getVmName());
|
volume.setVmName(srcVol.getVmName());
|
||||||
|
|
||||||
|
resource.createOrUpdateLogFileForCommand(cmd, Command.State.PROCESSING_IN_BACKEND);
|
||||||
final KVMPhysicalDisk newDisk = storagePoolMgr.copyPhysicalDisk(volume, path != null ? path : volumeName, primaryPool, cmd.getWaitInMillSeconds());
|
final KVMPhysicalDisk newDisk = storagePoolMgr.copyPhysicalDisk(volume, path != null ? path : volumeName, primaryPool, cmd.getWaitInMillSeconds());
|
||||||
|
resource.createOrUpdateLogFileForCommand(cmd, Command.State.COMPLETED);
|
||||||
|
|
||||||
storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
|
storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
|
||||||
|
|
||||||
@ -2556,22 +2564,30 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||||||
} else {
|
} else {
|
||||||
final String volumeName = UUID.randomUUID().toString();
|
final String volumeName = UUID.randomUUID().toString();
|
||||||
destVolumeName = volumeName + "." + destFormat.getFileExtension();
|
destVolumeName = volumeName + "." + destFormat.getFileExtension();
|
||||||
|
|
||||||
|
// Update path in the command for reconciliation
|
||||||
|
if (destData.getPath() == null) {
|
||||||
|
((VolumeObjectTO) destData).setPath(destVolumeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid());
|
destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid());
|
||||||
try {
|
try {
|
||||||
Volume.Type volumeType = srcVol.getVolumeType();
|
Volume.Type volumeType = srcVol.getVolumeType();
|
||||||
|
|
||||||
|
resource.createOrUpdateLogFileForCommand(cmd, Command.State.PROCESSING_IN_BACKEND);
|
||||||
if (srcVol.getPassphrase() != null && (Volume.Type.ROOT.equals(volumeType) || Volume.Type.DATADISK.equals(volumeType))) {
|
if (srcVol.getPassphrase() != null && (Volume.Type.ROOT.equals(volumeType) || Volume.Type.DATADISK.equals(volumeType))) {
|
||||||
volume.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS);
|
volume.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS);
|
||||||
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds(), srcVol.getPassphrase(), destVol.getPassphrase(), srcVol.getProvisioningType());
|
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds(), srcVol.getPassphrase(), destVol.getPassphrase(), srcVol.getProvisioningType());
|
||||||
} else {
|
} else {
|
||||||
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds());
|
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds());
|
||||||
}
|
}
|
||||||
|
resource.createOrUpdateLogFileForCommand(cmd, Command.State.COMPLETED);
|
||||||
} catch (Exception e) { // Any exceptions while copying the disk, should send failed answer with the error message
|
} catch (Exception e) { // Any exceptions while copying the disk, should send failed answer with the error message
|
||||||
String errMsg = String.format("Failed to copy volume [uuid: %s, name: %s] to dest storage [id: %s, name: %s], due to %s",
|
String errMsg = String.format("Failed to copy volume [uuid: %s, name: %s] to dest storage [id: %s, name: %s], due to %s",
|
||||||
srcVol.getUuid(), srcVol.getName(), destPrimaryStore.getUuid(), destPrimaryStore.getName(), e.toString());
|
srcVol.getUuid(), srcVol.getName(), destPrimaryStore.getUuid(), destPrimaryStore.getName(), e.toString());
|
||||||
logger.debug(errMsg, e);
|
logger.debug(errMsg, e);
|
||||||
|
resource.createOrUpdateLogFileForCommand(cmd, Command.State.FAILED);
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
} finally {
|
} finally {
|
||||||
if (srcPrimaryStore.isManaged()) {
|
if (srcPrimaryStore.isManaged()) {
|
||||||
|
|||||||
@ -0,0 +1,191 @@
|
|||||||
|
//
|
||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you 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.cloud.hypervisor.kvm.resource;
|
||||||
|
|
||||||
|
import com.cloud.hypervisor.kvm.resource.disconnecthook.DisconnectHook;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class DisconnectHooksTest {
|
||||||
|
class TestHook extends DisconnectHook {
|
||||||
|
private boolean _started = false;
|
||||||
|
private boolean _executed = false;
|
||||||
|
private boolean _withTimeout = false;
|
||||||
|
|
||||||
|
private long _runtime;
|
||||||
|
|
||||||
|
public TestHook() { super("foo"); }
|
||||||
|
|
||||||
|
public TestHook(long timeout, long runtime) {
|
||||||
|
super("foo", timeout);
|
||||||
|
_withTimeout = true;
|
||||||
|
_runtime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
_started = true;
|
||||||
|
if (this._withTimeout) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(this._runtime);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException("TestHook interrupted while sleeping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._executed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasRun() {
|
||||||
|
return _executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasStarted() {
|
||||||
|
return _started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LibvirtComputingResource libvirtComputingResource;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
libvirtComputingResource = new LibvirtComputingResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addHookWithoutRun() {
|
||||||
|
TestHook hook = new TestHook();
|
||||||
|
libvirtComputingResource = new LibvirtComputingResource();
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook);
|
||||||
|
|
||||||
|
// test that we added hook but did not run it
|
||||||
|
Assert.assertEquals(1, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
Assert.assertFalse(hook.hasRun());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addHookWithRun() {
|
||||||
|
TestHook hook = new TestHook();
|
||||||
|
libvirtComputingResource = new LibvirtComputingResource();
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook);
|
||||||
|
|
||||||
|
// test that we added hook but did not run it
|
||||||
|
Assert.assertEquals(1, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
Assert.assertFalse(hook.hasRun());
|
||||||
|
|
||||||
|
// test that we run and remove hook on disconnect
|
||||||
|
libvirtComputingResource.disconnected();
|
||||||
|
|
||||||
|
Assert.assertTrue(hook.hasRun());
|
||||||
|
Assert.assertEquals(0, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndRemoveHooksWithAndWithoutRun() {
|
||||||
|
TestHook hook1 = new TestHook();
|
||||||
|
TestHook hook2 = new TestHook();
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook1);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook2);
|
||||||
|
|
||||||
|
Assert.assertEquals(2, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
Assert.assertFalse(hook1.hasRun());
|
||||||
|
Assert.assertFalse(hook2.hasRun());
|
||||||
|
|
||||||
|
// remove first hook but leave second hook
|
||||||
|
libvirtComputingResource.removeDisconnectHook(hook1);
|
||||||
|
libvirtComputingResource.disconnected();
|
||||||
|
|
||||||
|
// ensure removed hook did not run
|
||||||
|
Assert.assertFalse(hook1.hasRun());
|
||||||
|
|
||||||
|
// ensure remaining hook did run
|
||||||
|
Assert.assertTrue(hook2.hasRun());
|
||||||
|
Assert.assertEquals(0, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndRunHooksOneWithTimeout() {
|
||||||
|
// test that hook stops running when we exceed timeout
|
||||||
|
long timeout = 500;
|
||||||
|
TestHook hook1 = new TestHook(timeout, timeout + 100);
|
||||||
|
TestHook hook2 = new TestHook();
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook1);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook2);
|
||||||
|
libvirtComputingResource.disconnected();
|
||||||
|
Assert.assertTrue(hook2.hasRun());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(timeout);
|
||||||
|
} catch (Exception ignored){}
|
||||||
|
|
||||||
|
Assert.assertTrue(hook1.hasStarted());
|
||||||
|
Assert.assertFalse(hook1.isAlive());
|
||||||
|
Assert.assertFalse(hook1.hasRun());
|
||||||
|
|
||||||
|
Assert.assertEquals(0, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndRunTwoHooksWithTimeout() {
|
||||||
|
// test that hooks stop running when we exceed timeout
|
||||||
|
// test for parallel timeout rather than additive
|
||||||
|
long timeout = 500;
|
||||||
|
TestHook hook1 = new TestHook(timeout, timeout + 100);
|
||||||
|
TestHook hook2 = new TestHook(timeout, timeout + 100);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook1);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook2);
|
||||||
|
libvirtComputingResource.disconnected();
|
||||||
|
|
||||||
|
// if the timeouts were additive (e.g. if we were sequentially looping through join(timeout)), the second Hook
|
||||||
|
// would get enough time to complete (500 for first Hook and 500 for itself) and not be interrupted.
|
||||||
|
try {
|
||||||
|
Thread.sleep(timeout*2);
|
||||||
|
} catch (Exception ignored){}
|
||||||
|
|
||||||
|
Assert.assertTrue(hook1.hasStarted());
|
||||||
|
Assert.assertFalse(hook1.isAlive());
|
||||||
|
Assert.assertFalse(hook1.hasRun());
|
||||||
|
Assert.assertTrue(hook2.hasStarted());
|
||||||
|
Assert.assertFalse(hook2.isAlive());
|
||||||
|
Assert.assertFalse(hook2.hasRun());
|
||||||
|
|
||||||
|
Assert.assertEquals(0, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addAndRunTimeoutHooksToCompletion() {
|
||||||
|
// test we can run to completion if we don't take as long as timeout, and they run parallel
|
||||||
|
long timeout = 500;
|
||||||
|
TestHook hook1 = new TestHook(timeout, timeout - 100);
|
||||||
|
TestHook hook2 = new TestHook(timeout, timeout - 100);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook1);
|
||||||
|
libvirtComputingResource.addDisconnectHook(hook2);
|
||||||
|
libvirtComputingResource.disconnected();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(timeout);
|
||||||
|
} catch (Exception ignored){}
|
||||||
|
|
||||||
|
Assert.assertTrue(hook1.hasStarted());
|
||||||
|
Assert.assertTrue(hook1.hasRun());
|
||||||
|
Assert.assertTrue(hook2.hasStarted());
|
||||||
|
Assert.assertTrue(hook2.hasRun());
|
||||||
|
Assert.assertEquals(0, libvirtComputingResource._disconnectHooks.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1760,6 +1760,8 @@ public class LibvirtComputingResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCheckVirtualMachineCommand() {
|
public void testCheckVirtualMachineCommand() {
|
||||||
final Connect conn = Mockito.mock(Connect.class);
|
final Connect conn = Mockito.mock(Connect.class);
|
||||||
|
final Domain vm = Mockito.mock(Domain.class);
|
||||||
|
final DomainInfo domainInfo = Mockito.mock(DomainInfo.class);
|
||||||
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class);
|
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class);
|
||||||
|
|
||||||
final String vmName = "Test";
|
final String vmName = "Test";
|
||||||
@ -1768,6 +1770,8 @@ public class LibvirtComputingResourceTest {
|
|||||||
when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
|
when(libvirtComputingResourceMock.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper);
|
||||||
try {
|
try {
|
||||||
when(libvirtUtilitiesHelper.getConnectionByVmName(vmName)).thenReturn(conn);
|
when(libvirtUtilitiesHelper.getConnectionByVmName(vmName)).thenReturn(conn);
|
||||||
|
when(conn.domainLookupByName(vmName)).thenReturn(vm);
|
||||||
|
when(vm.getInfo()).thenReturn(domainInfo);
|
||||||
} catch (final LibvirtException e) {
|
} catch (final LibvirtException e) {
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
@ -5521,7 +5525,7 @@ public class LibvirtComputingResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSetQuotaAndPeriod() {
|
public void testSetQuotaAndPeriod() {
|
||||||
double pct = 0.33d;
|
double pct = 0.33d;
|
||||||
Mockito.when(vmTO.getLimitCpuUse()).thenReturn(true);
|
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true);
|
||||||
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(pct);
|
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(pct);
|
||||||
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
||||||
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
||||||
@ -5532,7 +5536,7 @@ public class LibvirtComputingResourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetQuotaAndPeriodNoCpuLimitUse() {
|
public void testSetQuotaAndPeriodNoCpuLimitUse() {
|
||||||
Mockito.when(vmTO.getLimitCpuUse()).thenReturn(false);
|
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
|
||||||
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
||||||
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
||||||
lcr.setQuotaAndPeriod(vmTO, cpuTuneDef);
|
lcr.setQuotaAndPeriod(vmTO, cpuTuneDef);
|
||||||
@ -5543,7 +5547,7 @@ public class LibvirtComputingResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSetQuotaAndPeriodMinQuota() {
|
public void testSetQuotaAndPeriodMinQuota() {
|
||||||
double pct = 0.01d;
|
double pct = 0.01d;
|
||||||
Mockito.when(vmTO.getLimitCpuUse()).thenReturn(true);
|
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true);
|
||||||
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(pct);
|
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(pct);
|
||||||
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
CpuTuneDef cpuTuneDef = new CpuTuneDef();
|
||||||
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
final LibvirtComputingResource lcr = new LibvirtComputingResource();
|
||||||
|
|||||||
@ -1961,7 +1961,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
|||||||
// Check if license supports the feature
|
// Check if license supports the feature
|
||||||
VmwareHelper.isFeatureLicensed(hyperHost, FeatureKeyConstants.HOTPLUG);
|
VmwareHelper.isFeatureLicensed(hyperHost, FeatureKeyConstants.HOTPLUG);
|
||||||
VmwareHelper.setVmScaleUpConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) requestedMaxMemoryInMb, ramMb,
|
VmwareHelper.setVmScaleUpConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) requestedMaxMemoryInMb, ramMb,
|
||||||
vmSpec.getLimitCpuUse());
|
vmSpec.isLimitCpuUse());
|
||||||
|
|
||||||
if (!vmMo.configureVm(vmConfigSpec)) {
|
if (!vmMo.configureVm(vmConfigSpec)) {
|
||||||
throw new Exception("Unable to execute ScaleVmCommand");
|
throw new Exception("Unable to execute ScaleVmCommand");
|
||||||
@ -2188,7 +2188,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
|||||||
}
|
}
|
||||||
tearDownVm(vmMo);
|
tearDownVm(vmMo);
|
||||||
} else if (!hyperHost.createBlankVm(vmNameOnVcenter, vmInternalCSName, vmSpec.getCpus(), vmSpec.getMaxSpeed().intValue(), getReservedCpuMHZ(vmSpec),
|
} else if (!hyperHost.createBlankVm(vmNameOnVcenter, vmInternalCSName, vmSpec.getCpus(), vmSpec.getMaxSpeed().intValue(), getReservedCpuMHZ(vmSpec),
|
||||||
vmSpec.getLimitCpuUse(), (int) (vmSpec.getMaxRam() / ResourceType.bytesToMiB), getReservedMemoryMb(vmSpec), guestOsId, rootDiskDataStoreDetails.first(), false,
|
vmSpec.isLimitCpuUse(), (int) (vmSpec.getMaxRam() / ResourceType.bytesToMiB), getReservedMemoryMb(vmSpec), guestOsId, rootDiskDataStoreDetails.first(), false,
|
||||||
controllerInfo, systemVm)) {
|
controllerInfo, systemVm)) {
|
||||||
throw new Exception("Failed to create VM. vmName: " + vmInternalCSName);
|
throw new Exception("Failed to create VM. vmName: " + vmInternalCSName);
|
||||||
}
|
}
|
||||||
@ -2232,7 +2232,7 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
|||||||
VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[totalChangeDevices];
|
VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[totalChangeDevices];
|
||||||
DiskTO[] sortedDisks = sortVolumesByDeviceId(disks);
|
DiskTO[] sortedDisks = sortVolumesByDeviceId(disks);
|
||||||
VmwareHelper.setBasicVmConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) (vmSpec.getMaxRam() / (1024 * 1024)),
|
VmwareHelper.setBasicVmConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) (vmSpec.getMaxRam() / (1024 * 1024)),
|
||||||
getReservedMemoryMb(vmSpec), guestOsId, vmSpec.getLimitCpuUse(), deployAsIs);
|
getReservedMemoryMb(vmSpec), guestOsId, vmSpec.isLimitCpuUse(), deployAsIs);
|
||||||
|
|
||||||
// Check for multi-cores per socket settings
|
// Check for multi-cores per socket settings
|
||||||
int numCoresPerSocket = 1;
|
int numCoresPerSocket = 1;
|
||||||
|
|||||||
@ -1387,7 +1387,7 @@ public abstract class CitrixResourceBase extends ServerResourceBase implements S
|
|||||||
cpuWeight = _maxWeight;
|
cpuWeight = _maxWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vmSpec.getLimitCpuUse()) {
|
if (vmSpec.isLimitCpuUse()) {
|
||||||
// CPU cap is per VM, so need to assign cap based on the number
|
// CPU cap is per VM, so need to assign cap based on the number
|
||||||
// of vcpus
|
// of vcpus
|
||||||
utilization = (int)(vmSpec.getMaxSpeed() * 0.99 * vmSpec.getCpus() / _host.getSpeed() * 100);
|
utilization = (int)(vmSpec.getMaxSpeed() * 0.99 * vmSpec.getCpus() / _host.getSpeed() * 100);
|
||||||
@ -4709,7 +4709,7 @@ public abstract class CitrixResourceBase extends ServerResourceBase implements S
|
|||||||
cpuWeight = _maxWeight;
|
cpuWeight = _maxWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vmSpec.getLimitCpuUse()) {
|
if (vmSpec.isLimitCpuUse()) {
|
||||||
long utilization; // max CPU cap, default is unlimited
|
long utilization; // max CPU cap, default is unlimited
|
||||||
utilization = (int)(vmSpec.getMaxSpeed() * 0.99 * vmSpec.getCpus() / _host.getSpeed() * 100);
|
utilization = (int)(vmSpec.getMaxSpeed() * 0.99 * vmSpec.getCpus() / _host.getSpeed() * 100);
|
||||||
// vm.addToVCPUsParamsLive(conn, "cap",
|
// vm.addToVCPUsParamsLive(conn, "cap",
|
||||||
|
|||||||
@ -62,6 +62,7 @@ import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
|
|||||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||||
import org.apache.cloudstack.storage.volume.VolumeObject;
|
import org.apache.cloudstack.storage.volume.VolumeObject;
|
||||||
|
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -79,6 +80,7 @@ import com.cloud.agent.api.to.DiskTO;
|
|||||||
import com.cloud.agent.api.to.StorageFilerTO;
|
import com.cloud.agent.api.to.StorageFilerTO;
|
||||||
import com.cloud.alert.AlertManager;
|
import com.cloud.alert.AlertManager;
|
||||||
import com.cloud.configuration.Config;
|
import com.cloud.configuration.Config;
|
||||||
|
import com.cloud.exception.OperationTimedoutException;
|
||||||
import com.cloud.host.Host;
|
import com.cloud.host.Host;
|
||||||
import com.cloud.host.HostVO;
|
import com.cloud.host.HostVO;
|
||||||
import com.cloud.host.dao.HostDao;
|
import com.cloud.host.dao.HostDao;
|
||||||
@ -893,9 +895,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
boolean migrateStatus = answer.getResult();
|
boolean migrateStatus = answer.getResult();
|
||||||
|
|
||||||
if (migrateStatus) {
|
if (migrateStatus) {
|
||||||
updateVolumeAfterCopyVolume(srcData, destData);
|
updateAfterSuccessfulVolumeMigration(srcData, destData, host);
|
||||||
updateSnapshotsAfterCopyVolume(srcData, destData);
|
|
||||||
deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
|
|
||||||
logger.debug("Successfully migrated migrate PowerFlex volume {} to storage pool {}", srcData, destStore);
|
logger.debug("Successfully migrated migrate PowerFlex volume {} to storage pool {}", srcData, destStore);
|
||||||
answer = new Answer(null, true, null);
|
answer = new Answer(null, true, null);
|
||||||
} else {
|
} else {
|
||||||
@ -906,6 +906,17 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to migrate PowerFlex volume: {} due to: {}", srcData, e.getMessage());
|
logger.error("Failed to migrate PowerFlex volume: {} due to: {}", srcData, e.getMessage());
|
||||||
answer = new Answer(null, false, e.getMessage());
|
answer = new Answer(null, false, e.getMessage());
|
||||||
|
if (e.getMessage().contains(OperationTimedoutException.class.getName())) {
|
||||||
|
logger.error(String.format("The PowerFlex volume %s might have been migrated because the exception is %s, checking the volume on destination pool", srcData, OperationTimedoutException.class.getName()));
|
||||||
|
Boolean volumeOnDestination = getVolumeStateOnPool(destStore, destVolumePath);
|
||||||
|
if (volumeOnDestination) {
|
||||||
|
logger.error(String.format("The PowerFlex volume %s has been migrated to destination pool %s", srcData, destStore.getName()));
|
||||||
|
updateAfterSuccessfulVolumeMigration(srcData, destData, host);
|
||||||
|
answer = new Answer(null, true, null);
|
||||||
|
} else {
|
||||||
|
logger.error(String.format("The PowerFlex volume %s has not been migrated completely to destination pool %s", srcData, destStore.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destVolumePath != null && !answer.getResult()) {
|
if (destVolumePath != null && !answer.getResult()) {
|
||||||
@ -915,6 +926,40 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateAfterSuccessfulVolumeMigration(DataObject srcData, DataObject destData, Host host) {
|
||||||
|
try {
|
||||||
|
updateVolumeAfterCopyVolume(srcData, destData);
|
||||||
|
updateSnapshotsAfterCopyVolume(srcData, destData);
|
||||||
|
deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error(String.format("Error while update PowerFlex volume: %s after successfully migration due to: %s", srcData, ex.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getVolumeStateOnPool(DataStore srcStore, String srcVolumePath) {
|
||||||
|
try {
|
||||||
|
// check the state of volume on pool via ScaleIO gateway
|
||||||
|
final ScaleIOGatewayClient client = getScaleIOClient(srcStore);
|
||||||
|
final String sourceScaleIOVolumeId = ScaleIOUtil.getVolumePath(srcVolumePath);
|
||||||
|
final org.apache.cloudstack.storage.datastore.api.Volume sourceScaleIOVolume = client.getVolume(sourceScaleIOVolumeId);
|
||||||
|
logger.debug(String.format("The PowerFlex volume %s on pool %s is: %s", srcVolumePath, srcStore.getName(),
|
||||||
|
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(sourceScaleIOVolume, "id", "name", "vtreeId", "sizeInGB", "volumeSizeInGb")));
|
||||||
|
if (sourceScaleIOVolume == null || StringUtils.isEmpty(sourceScaleIOVolume.getVtreeId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Pair<Long, Long> volumeStats = getVolumeStats(storagePoolDao.findById(srcStore.getId()), srcVolumePath);
|
||||||
|
if (volumeStats == null) {
|
||||||
|
logger.debug(String.format("Unable to find volume stats for %s on pool %s", srcVolumePath, srcStore.getName()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.debug(String.format("Found volume stats for %s: provisionedSizeInBytes = %s, allocatedSizeInBytes = %s on pool %s", srcVolumePath, volumeStats.first(), volumeStats.second(), srcStore.getName()));
|
||||||
|
return volumeStats.first().equals(volumeStats.second());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error(String.format("Failed to check if PowerFlex volume %s exists on source pool %s", srcVolumePath, srcStore.getName()));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateVolumeAfterCopyVolume(DataObject srcData, DataObject destData) {
|
protected void updateVolumeAfterCopyVolume(DataObject srcData, DataObject destData) {
|
||||||
// destination volume is already created and volume path is set in database by this time at "CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId());"
|
// destination volume is already created and volume path is set in database by this time at "CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId());"
|
||||||
final long srcVolumeId = srcData.getId();
|
final long srcVolumeId = srcData.getId();
|
||||||
|
|||||||
@ -379,7 +379,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
|||||||
protected void wakeupWorkers() {
|
protected void wakeupWorkers() {
|
||||||
logger.debug("Wakeup workers HA");
|
logger.debug("Wakeup workers HA");
|
||||||
for (WorkerThread worker : _workers) {
|
for (WorkerThread worker : _workers) {
|
||||||
worker.wakup();
|
worker.wakeup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,6 +589,10 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
|||||||
vm.getUpdated() + " previous updated = " + work.getUpdateTime());
|
vm.getUpdated() + " previous updated = " + work.getUpdateTime());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (vm.getHostId() != null && !vm.getHostId().equals(work.getHostId())) {
|
||||||
|
logger.info("VM " + vm + " has been changed. Current host id = " + vm.getHostId() + " Previous host id = " + work.getHostId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM;
|
AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM;
|
||||||
if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) {
|
if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) {
|
||||||
@ -1209,7 +1213,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void wakup() {
|
public synchronized void wakeup() {
|
||||||
notifyAll();
|
notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,7 +128,7 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
|
|||||||
* @param vmProfile vm profile
|
* @param vmProfile vm profile
|
||||||
*/
|
*/
|
||||||
protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) {
|
protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) {
|
||||||
if (to.getLimitCpuUse()) {
|
if (to.isLimitCpuUse()) {
|
||||||
VirtualMachine vm = vmProfile.getVirtualMachine();
|
VirtualMachine vm = vmProfile.getVirtualMachine();
|
||||||
HostVO host = hostDao.findById(vm.getHostId());
|
HostVO host = hostDao.findById(vm.getHostId());
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -382,4 +382,7 @@
|
|||||||
<bean id="sharedFSServiceImpl" class="org.apache.cloudstack.storage.sharedfs.SharedFSServiceImpl">
|
<bean id="sharedFSServiceImpl" class="org.apache.cloudstack.storage.sharedfs.SharedFSServiceImpl">
|
||||||
<property name="sharedFSProviders" value="#{sharedFSProvidersRegistry.registered}" />
|
<property name="sharedFSProviders" value="#{sharedFSProvidersRegistry.registered}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="reconcileCommandServiceImpl" class="org.apache.cloudstack.command.ReconcileCommandServiceImpl">
|
||||||
|
</bean>
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -114,7 +114,7 @@ public class KVMGuruTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws UnsupportedEncodingException {
|
public void setup() throws UnsupportedEncodingException {
|
||||||
Mockito.when(vmTO.getLimitCpuUse()).thenReturn(true);
|
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true);
|
||||||
Mockito.when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
Mockito.when(vmProfile.getVirtualMachine()).thenReturn(vm);
|
||||||
Mockito.when(vm.getHostId()).thenReturn(hostId);
|
Mockito.when(vm.getHostId()).thenReturn(hostId);
|
||||||
Mockito.when(hostDao.findById(hostId)).thenReturn(host);
|
Mockito.when(hostDao.findById(hostId)).thenReturn(host);
|
||||||
@ -156,7 +156,7 @@ public class KVMGuruTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetVmQuotaPercentageNotCPULimit() {
|
public void testSetVmQuotaPercentageNotCPULimit() {
|
||||||
Mockito.when(vmTO.getLimitCpuUse()).thenReturn(false);
|
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
|
||||||
guru.setVmQuotaPercentage(vmTO, vmProfile);
|
guru.setVmQuotaPercentage(vmTO, vmProfile);
|
||||||
Mockito.verify(vmProfile, Mockito.never()).getVirtualMachine();
|
Mockito.verify(vmProfile, Mockito.never()).getVirtualMachine();
|
||||||
Mockito.verify(vmTO, Mockito.never()).setCpuQuotaPercentage(Mockito.anyDouble());
|
Mockito.verify(vmTO, Mockito.never()).setCpuQuotaPercentage(Mockito.anyDouble());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user