[KVM] Rolling maintenance (#3610)

This commit is contained in:
Nicolas Vazquez 2020-03-12 12:59:46 -03:00 committed by GitHub
parent 016644d507
commit efe00aa7e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2600 additions and 12 deletions

View File

@ -0,0 +1,91 @@
#!/usr/bin/python
# 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.
from subprocess import *
import sys
import logging
LOG_FILE='/var/log/cloudstack/agent/rolling-maintenance.log'
AVOID_MAINTENANCE_EXIT_STATUS=70
logging.basicConfig(filename=LOG_FILE,
filemode='a',
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%H:%M:%S',
level=logging.INFO)
logger = logging.getLogger('rolling-maintenance')
def execute_script(stage, script, payload, timeout):
logger.info("Executing script: %s for stage: %s" % (script, stage))
try:
command = "timeout %s %s " % (str(timeout), script)
if payload:
logger.info("Adding payload: %s" % payload)
command += " " + payload
pout = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
exitStatus = pout.wait()
stdout, stderr = pout.communicate()
success = True if exitStatus == 0 or exitStatus == AVOID_MAINTENANCE_EXIT_STATUS else False
avoid_maintenance = True if exitStatus == AVOID_MAINTENANCE_EXIT_STATUS else False
return {"success": success, "message": stdout.strip(), "avoidmaintenance": avoid_maintenance}
except Exception as e:
logger.error("Error in stage %s: %s" % (script, e))
sys.exit(1)
if __name__ == '__main__':
try:
logger.info(sys.argv)
if len(sys.argv) < 2:
logger.error("Arguments missing")
sys.exit(0)
args = sys.argv[1]
params = args.split(',')
if len(params) < 5:
logger.error("Wrong number of parameters received, STAGE,SCRIPT,TIMEOUT,RESULTS_FILE,OUTPUT_FILE"
"[,PAYLOAD] expected")
sys.exit(0)
stage = params[0]
script = params[1]
timeout = params[2]
results_file_path = params[3]
output_file_path = params[4]
payload = params[5] if len(params) > 5 else None
logger.info("Received parameters: stage: %s, script: %s, timeout: %s, results_file: %s, output_file: %s "
"and payload: %s" % (stage, script, timeout, results_file_path, output_file_path, payload))
results = execute_script(stage, script, payload, timeout)
# Persist results and output on a file
output_file = open(output_file_path, "w+")
output_file.write(results['message'])
output_file.close()
results_file = open(results_file_path, "w+")
results_file.write("%s,%s,%s" % (stage, str(results['success']), str(results['avoidmaintenance'])))
results_file.close()
msg = "Successful execution of %s" if results['success'] else "Script execution failed: %s"
logger.info(results['message'])
logger.info(msg % script)
except Exception as e:
logger.error("Unexpected error on systemd service: %s" % e)
sys.exit(1)

View File

@ -118,6 +118,12 @@ hypervisor.type=kvm
# This parameter specifies a directory on the host local storage for temporary storing direct download templates # This parameter specifies a directory on the host local storage for temporary storing direct download templates
#direct.download.temporary.download.location=/var/lib/libvirt/images #direct.download.temporary.download.location=/var/lib/libvirt/images
# set the rolling maintenance hook scripts directory
#rolling.maintenance.hooks.dir=/etc/cloudstack/agent/hooks.d
# disable the rolling maintenance service execution
#rolling.maintenance.service.executor.disabled=true
# set the hypervisor URI. Usually there is no need for changing this # set the hypervisor URI. Usually there is no need for changing this
# For KVM: qemu:///system # For KVM: qemu:///system
# For LXC: lxc:/// # For LXC: lxc:///

View File

@ -15,7 +15,7 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
/var/log/cloudstack/agent/security_group.log /var/log/cloudstack/agent/resizevolume.log { /var/log/cloudstack/agent/security_group.log /var/log/cloudstack/agent/resizevolume.log /var/log/cloudstack/agent/rolling-maintenance.log {
copytruncate copytruncate
daily daily
rotate 5 rotate 5

View File

@ -33,6 +33,7 @@ public class DataCenterDeployment implements DeploymentPlan {
boolean _recreateDisks; boolean _recreateDisks;
ReservationContext _context; ReservationContext _context;
List<Long> preferredHostIds = new ArrayList<>(); List<Long> preferredHostIds = new ArrayList<>();
boolean migrationPlan;
public DataCenterDeployment(long dataCenterId) { public DataCenterDeployment(long dataCenterId) {
this(dataCenterId, null, null, null, null, null); this(dataCenterId, null, null, null, null, null);
@ -107,4 +108,13 @@ public class DataCenterDeployment implements DeploymentPlan {
return this.preferredHostIds; return this.preferredHostIds;
} }
public void setMigrationPlan(boolean migrationPlan) {
this.migrationPlan = migrationPlan;
}
@Override
public boolean isMigrationPlan() {
return migrationPlan;
}
} }

View File

@ -71,4 +71,6 @@ public interface DeploymentPlan {
void setPreferredHosts(List<Long> hostIds); void setPreferredHosts(List<Long> hostIds);
List<Long> getPreferredHosts(); List<Long> getPreferredHosts();
boolean isMigrationPlan();
} }

View File

@ -76,6 +76,10 @@ import com.cloud.user.User;
import com.cloud.vm.Nic; import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.PodResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
public class EventTypes { public class EventTypes {
@ -591,6 +595,13 @@ public class EventTypes {
// Diagnostics Events // Diagnostics Events
public static final String EVENT_SYSTEM_VM_DIAGNOSTICS = "SYSTEM.VM.DIAGNOSTICS"; public static final String EVENT_SYSTEM_VM_DIAGNOSTICS = "SYSTEM.VM.DIAGNOSTICS";
// Rolling Maintenance
public static final String EVENT_START_ROLLING_MAINTENANCE = "SYSTEM.ROLLING.MAINTENANCE";
public static final String EVENT_HOST_ROLLING_MAINTENANCE = "HOST.ROLLING.MAINTENANCE";
public static final String EVENT_CLUSTER_ROLLING_MAINTENANCE = "CLUSTER.ROLLING.MAINTENANCE";
public static final String EVENT_POD_ROLLING_MAINTENANCE = "POD.ROLLING.MAINTENANCE";
public static final String EVENT_ZONE_ROLLING_MAINTENANCE = "ZONE.ROLLING.MAINTENANCE";
static { static {
// TODO: need a way to force author adding event types to declare the entity details as well, with out braking // TODO: need a way to force author adding event types to declare the entity details as well, with out braking
@ -990,6 +1001,11 @@ public class EventTypes {
entityEventDetails.put(EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE, VirtualMachineTemplate.class); entityEventDetails.put(EVENT_TEMPLATE_DIRECT_DOWNLOAD_FAILURE, VirtualMachineTemplate.class);
entityEventDetails.put(EVENT_ISO_DIRECT_DOWNLOAD_FAILURE, "Iso"); entityEventDetails.put(EVENT_ISO_DIRECT_DOWNLOAD_FAILURE, "Iso");
entityEventDetails.put(EVENT_SYSTEM_VM_DIAGNOSTICS, VirtualMachine.class); entityEventDetails.put(EVENT_SYSTEM_VM_DIAGNOSTICS, VirtualMachine.class);
entityEventDetails.put(EVENT_ZONE_ROLLING_MAINTENANCE, ZoneResponse.class);
entityEventDetails.put(EVENT_POD_ROLLING_MAINTENANCE, PodResponse.class);
entityEventDetails.put(EVENT_CLUSTER_ROLLING_MAINTENANCE, ClusterResponse.class);
entityEventDetails.put(EVENT_HOST_ROLLING_MAINTENANCE, HostResponse.class);
} }
public static String getEntityForEvent(String eventName) { public static String getEntityForEvent(String eventName) {

View File

@ -0,0 +1,146 @@
// 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.resource;
import com.cloud.host.Host;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import java.util.Date;
import java.util.List;
public interface RollingMaintenanceManager extends Configurable {
ConfigKey<Integer> KvmRollingMaintenanceStageTimeout = new ConfigKey<>("Advanced", Integer.class,
"kvm.rolling.maintenance.stage.timeout", "1800",
"Wait timeout (in seconds) for a rolling maintenance stage update from hosts",
true, ConfigKey.Scope.Global);
ConfigKey<Integer> KvmRollingMaintenancePingInterval = new ConfigKey<>("Advanced", Integer.class,
"kvm.rolling.maintenance.ping.interval", "10",
"Ping interval in seconds between management server and hosts performing stages during rolling maintenance",
true, ConfigKey.Scope.Global);
ConfigKey<Integer> KvmRollingMaintenanceWaitForMaintenanceTimeout = new ConfigKey<>("Advanced", Integer.class,
"kvm.rolling.maintenance.wait.maintenance.timeout", "1800",
"Timeout (in seconds) to wait for a host preparing to enter maintenance mode",
true, ConfigKey.Scope.Global);
class HostSkipped {
private Host host;
private String reason;
public HostSkipped(Host host, String reason) {
this.host = host;
this.reason = reason;
}
public Host getHost() {
return host;
}
public void setHost(Host host) {
this.host = host;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}
class HostUpdated {
private Host host;
private Date start;
private Date end;
private String outputMsg;
public HostUpdated(Host host, Date start, Date end, String outputMsg) {
this.host = host;
this.start = start;
this.end = end;
this.outputMsg = outputMsg;
}
public Host getHost() {
return host;
}
public void setHost(Host host) {
this.host = host;
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date end) {
this.end = end;
}
public String getOutputMsg() {
return outputMsg;
}
public void setOutputMsg(String outputMsg) {
this.outputMsg = outputMsg;
}
}
enum Stage {
PreFlight, PreMaintenance, Maintenance, PostMaintenance;
public Stage next() {
switch (this) {
case PreFlight:
return PreMaintenance;
case PreMaintenance:
return Maintenance;
case Maintenance:
return PostMaintenance;
case PostMaintenance:
return null;
}
throw new CloudRuntimeException("Unexpected stage: " + this);
}
}
enum ResourceType {
Pod, Cluster, Zone, Host
}
/**
* Starts rolling maintenance as specified in cmd
* @param cmd command
* @return tuple: (SUCCESS, DETAILS, (HOSTS_UPDATED, HOSTS_SKIPPED))
*/
Ternary<Boolean, String, Pair<List<HostUpdated>, List<HostSkipped>>> startRollingMaintenance(StartRollingMaintenanceCmd cmd);
Pair<ResourceType, List<Long>> getResourceTypeIdPair(StartRollingMaintenanceCmd cmd);
}

View File

@ -74,6 +74,7 @@ public class ApiConstants {
public static final String CLEANUP = "cleanup"; public static final String CLEANUP = "cleanup";
public static final String MAKEREDUNDANT = "makeredundant"; public static final String MAKEREDUNDANT = "makeredundant";
public static final String CLUSTER_ID = "clusterid"; public static final String CLUSTER_ID = "clusterid";
public static final String CLUSTER_IDS = "clusterids";
public static final String CLUSTER_NAME = "clustername"; public static final String CLUSTER_NAME = "clustername";
public static final String CLUSTER_TYPE = "clustertype"; public static final String CLUSTER_TYPE = "clustertype";
public static final String CN = "cn"; public static final String CN = "cn";
@ -173,6 +174,7 @@ public class ApiConstants {
public static final String HEALTH = "health"; public static final String HEALTH = "health";
public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage"; public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage";
public static final String HOST_ID = "hostid"; public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids";
public static final String HOST_NAME = "hostname"; public static final String HOST_NAME = "hostname";
public static final String HYPERVISOR = "hypervisor"; public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline"; public static final String INLINE = "inline";
@ -256,6 +258,7 @@ public class ApiConstants {
public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor"; public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor";
public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate"; public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate";
public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled"; public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled";
public static final String OUTPUT = "output";
public static final String OVF_PROPERTIES = "ovfproperties"; public static final String OVF_PROPERTIES = "ovfproperties";
public static final String PARAMS = "params"; public static final String PARAMS = "params";
public static final String PARENT_ID = "parentid"; public static final String PARENT_ID = "parentid";
@ -267,6 +270,7 @@ public class ApiConstants {
public static final String PASSWORD_ENABLED = "passwordenabled"; public static final String PASSWORD_ENABLED = "passwordenabled";
public static final String SSHKEY_ENABLED = "sshkeyenabled"; public static final String SSHKEY_ENABLED = "sshkeyenabled";
public static final String PATH = "path"; public static final String PATH = "path";
public static final String PAYLOAD = "payload";
public static final String POD_ID = "podid"; public static final String POD_ID = "podid";
public static final String POD_NAME = "podname"; public static final String POD_NAME = "podname";
public static final String POD_IDS = "podids"; public static final String POD_IDS = "podids";

View File

@ -22,6 +22,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import com.cloud.resource.RollingMaintenanceManager;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails;
@ -88,7 +92,6 @@ import org.apache.cloudstack.api.response.RemoteAccessVpnResponse;
import org.apache.cloudstack.api.response.ResourceCountResponse; import org.apache.cloudstack.api.response.ResourceCountResponse;
import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import org.apache.cloudstack.api.response.SSHKeyPairResponse; import org.apache.cloudstack.api.response.SSHKeyPairResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse;
@ -125,7 +128,6 @@ import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule;
import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIp;
import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.region.PortableIpRange;
@ -482,4 +484,7 @@ public interface ResponseGenerator {
ManagementServerResponse createManagementResponse(ManagementServerHost mgmt); ManagementServerResponse createManagementResponse(ManagementServerHost mgmt);
List<RouterHealthCheckResultResponse> createHealthCheckResponse(VirtualMachine router, List<RouterHealthCheckResult> healthCheckResults); List<RouterHealthCheckResultResponse> createHealthCheckResponse(VirtualMachine router, List<RouterHealthCheckResult> healthCheckResults);
RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List<RollingMaintenanceManager.HostUpdated> hostsUpdated, List<RollingMaintenanceManager.HostSkipped> hostsSkipped);
} }

View File

@ -97,6 +97,10 @@ public class PrepareForMaintenanceCmd extends BaseAsyncCmd {
return getId(); return getId();
} }
public void setId(Long id) {
this.id = id;
}
@Override @Override
public void execute() { public void execute() {
try { try {

View File

@ -0,0 +1,178 @@
// 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.api.command.admin.resource;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.resource.RollingMaintenanceManager;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.PodResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.List;
@APICommand(name = StartRollingMaintenanceCmd.APINAME, description = "Start rolling maintenance",
responseObject = RollingMaintenanceResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
authorized = {RoleType.Admin})
public class StartRollingMaintenanceCmd extends BaseAsyncCmd {
@Inject
RollingMaintenanceManager manager;
public static final Logger s_logger = Logger.getLogger(StartRollingMaintenanceCmd.class.getName());
public static final String APINAME = "startRollingMaintenance";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.POD_IDS, type = CommandType.LIST, collectionType = CommandType.UUID,
entityType = PodResponse.class, description = "the IDs of the pods to start maintenance on")
private List<Long> podIds;
@Parameter(name = ApiConstants.CLUSTER_IDS, type = CommandType.LIST, collectionType = CommandType.UUID,
entityType = ClusterResponse.class, description = "the IDs of the clusters to start maintenance on")
private List<Long> clusterIds;
@Parameter(name = ApiConstants.ZONE_ID_LIST, type = CommandType.LIST, collectionType = CommandType.UUID,
entityType = ZoneResponse.class, description = "the IDs of the zones to start maintenance on")
private List<Long> zoneIds;
@Parameter(name = ApiConstants.HOST_IDS, type = CommandType.LIST, collectionType = CommandType.UUID,
entityType = HostResponse.class, description = "the IDs of the hosts to start maintenance on")
private List<Long> hostIds;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN,
description = "if rolling mechanism should continue in case of an error")
private Boolean forced;
@Parameter(name = ApiConstants.PAYLOAD, type = CommandType.STRING,
description = "the command to execute while hosts are on maintenance")
private String payload;
@Parameter(name = ApiConstants.TIMEOUT, type = CommandType.INTEGER,
description = "optional operation timeout (in seconds) that overrides the global timeout setting")
private Integer timeout;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public List<Long> getPodIds() {
return podIds;
}
public List<Long> getClusterIds() {
return clusterIds;
}
public List<Long> getZoneIds() {
return zoneIds;
}
public List<Long> getHostIds() {
return hostIds;
}
public Boolean getForced() {
return forced != null && forced;
}
public String getPayload() {
return payload;
}
public Integer getTimeout() {
return timeout;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
Ternary<Boolean, String, Pair<List<RollingMaintenanceManager.HostUpdated>, List<RollingMaintenanceManager.HostSkipped>>>
result = manager.startRollingMaintenance(this);
Boolean success = result.first();
String details = result.second();
Pair<List<RollingMaintenanceManager.HostUpdated>, List<RollingMaintenanceManager.HostSkipped>> pair = result.third();
List<RollingMaintenanceManager.HostUpdated> hostsUpdated = pair.first();
List<RollingMaintenanceManager.HostSkipped> hostsSkipped = pair.second();
RollingMaintenanceResponse response = _responseGenerator.createRollingMaintenanceResponse(success, details, hostsUpdated, hostsSkipped);
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
@Override
public String getEventType() {
Pair<RollingMaintenanceManager.ResourceType, List<Long>> pair = manager.getResourceTypeIdPair(this);
RollingMaintenanceManager.ResourceType type = pair.first();
String eventType = "";
switch (type) {
case Zone:
eventType = EventTypes.EVENT_ZONE_ROLLING_MAINTENANCE;
break;
case Pod:
eventType = EventTypes.EVENT_POD_ROLLING_MAINTENANCE;
break;
case Cluster:
eventType = EventTypes.EVENT_CLUSTER_ROLLING_MAINTENANCE;
break;
case Host:
eventType = EventTypes.EVENT_HOST_ROLLING_MAINTENANCE;
}
return eventType;
}
@Override
public String getEventDescription() {
Pair<RollingMaintenanceManager.ResourceType, List<Long>> pair = manager.getResourceTypeIdPair(this);
return "Starting rolling maintenance on entity: " + pair.first() + " with IDs: " + pair.second();
}
}

View File

@ -0,0 +1,61 @@
// 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.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
public class RollingMaintenanceHostSkippedResponse extends BaseResponse {
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "the ID of the skipped host")
private String hostId;
@SerializedName(ApiConstants.HOST_NAME)
@Param(description = "the name of the skipped host")
private String hostName;
@SerializedName(ApiConstants.ACL_REASON)
@Param(description = "the reason to skip the host")
private String reason;
public String getHostId() {
return hostId;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}

View File

@ -0,0 +1,85 @@
// 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.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
public class RollingMaintenanceHostUpdatedResponse extends BaseResponse {
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "the ID of the updated host")
private String hostId;
@SerializedName(ApiConstants.HOST_NAME)
@Param(description = "the name of the updated host")
private String hostName;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "start date of the update on the host")
private String startDate;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "end date of the update on the host")
private String endDate;
@SerializedName(ApiConstants.OUTPUT)
@Param(description = "output of the maintenance script on the host")
private String output;
public String getHostId() {
return hostId;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public String getStartDate() {
return startDate;
}
public void setStartDate(String startDate) {
this.startDate = startDate;
}
public String getEndDate() {
return endDate;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
}

View File

@ -0,0 +1,79 @@
// 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.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.BaseResponse;
import java.util.List;
public class RollingMaintenanceResponse extends BaseResponse {
@SerializedName("success")
@Param(description = "indicates if the rolling maintenance operation was successful")
private Boolean success;
@SerializedName("details")
@Param(description = "in case of failure, details are displayed")
private String details;
@SerializedName("hostsupdated")
@Param(description = "the hosts updated", responseObject = RollingMaintenanceHostUpdatedResponse.class)
private List<RollingMaintenanceHostUpdatedResponse> updatedHosts;
@SerializedName("hostsskipped")
@Param(description = "the hosts skipped", responseObject = RollingMaintenanceHostSkippedResponse.class)
private List<RollingMaintenanceHostSkippedResponse> skippedHosts;
public RollingMaintenanceResponse(Boolean success, String details) {
this.success = success;
this.details = details;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public List<RollingMaintenanceHostUpdatedResponse> getUpdatedHosts() {
return updatedHosts;
}
public void setUpdatedHosts(List<RollingMaintenanceHostUpdatedResponse> updatedHosts) {
this.updatedHosts = updatedHosts;
}
public List<RollingMaintenanceHostSkippedResponse> getSkippedHosts() {
return skippedHosts;
}
public void setSkippedHosts(List<RollingMaintenanceHostSkippedResponse> skippedHosts) {
this.skippedHosts = skippedHosts;
}
}

View File

@ -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 com.cloud.agent.api;
public class RollingMaintenanceAnswer extends Answer {
private boolean finished;
private boolean avoidMaintenance;
private boolean maintenaceScriptDefined;
public RollingMaintenanceAnswer(Command command, boolean success, String details, boolean finished) {
super(command, success, details);
this.finished = finished;
}
public RollingMaintenanceAnswer(Command command, boolean isMaintenanceScript) {
super(command, true, "");
this.maintenaceScriptDefined = isMaintenanceScript;
}
public boolean isFinished() {
return finished;
}
public boolean isAvoidMaintenance() {
return avoidMaintenance;
}
public void setAvoidMaintenance(boolean avoidMaintenance) {
this.avoidMaintenance = avoidMaintenance;
}
public boolean isMaintenaceScriptDefined() {
return maintenaceScriptDefined;
}
public void setMaintenaceScriptDefined(boolean maintenaceScriptDefined) {
this.maintenaceScriptDefined = maintenaceScriptDefined;
}
}

View File

@ -0,0 +1,70 @@
//
// 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.agent.api;
public class RollingMaintenanceCommand extends Command {
private String stage;
private String payload;
private boolean started;
private boolean checkMaintenanceScript;
public RollingMaintenanceCommand(boolean checkMaintenanceScript) {
this.checkMaintenanceScript = checkMaintenanceScript;
}
public RollingMaintenanceCommand(String stage) {
this.stage = stage;
}
public void setStage(String stage) {
this.stage = stage;
}
public String getStage() {
return this.stage;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
public boolean isStarted() {
return started;
}
public void setStarted(boolean started) {
this.started = started;
}
public boolean isCheckMaintenanceScript() {
return checkMaintenanceScript;
}
@Override
public boolean executeInSequence() {
return false;
}
}

4
debian/rules vendored
View File

@ -45,6 +45,7 @@ override_dh_auto_install:
install -d -m0755 debian/$(PACKAGE)-agent/lib/systemd/system install -d -m0755 debian/$(PACKAGE)-agent/lib/systemd/system
install -m0644 packaging/systemd/$(PACKAGE)-agent.service debian/$(PACKAGE)-agent/lib/systemd/system/$(PACKAGE)-agent.service install -m0644 packaging/systemd/$(PACKAGE)-agent.service debian/$(PACKAGE)-agent/lib/systemd/system/$(PACKAGE)-agent.service
install -m0644 packaging/systemd/$(PACKAGE)-agent.default $(DESTDIR)/$(SYSCONFDIR)/default/$(PACKAGE)-agent install -m0644 packaging/systemd/$(PACKAGE)-agent.default $(DESTDIR)/$(SYSCONFDIR)/default/$(PACKAGE)-agent
install -m0644 packaging/systemd/$(PACKAGE)-rolling-maintenance@.service debian/$(PACKAGE)-agent/lib/systemd/system/$(PACKAGE)-rolling-maintenance@.service
install -D -m0644 agent/target/transformed/cloudstack-agent.logrotate $(DESTDIR)/$(SYSCONFDIR)/logrotate.d/cloudstack-agent install -D -m0644 agent/target/transformed/cloudstack-agent.logrotate $(DESTDIR)/$(SYSCONFDIR)/logrotate.d/cloudstack-agent
@ -54,6 +55,7 @@ override_dh_auto_install:
install -D agent/target/transformed/cloudstack-agent-upgrade $(DESTDIR)/usr/bin/cloudstack-agent-upgrade install -D agent/target/transformed/cloudstack-agent-upgrade $(DESTDIR)/usr/bin/cloudstack-agent-upgrade
install -D agent/target/transformed/cloud-guest-tool $(DESTDIR)/usr/bin/cloudstack-guest-tool install -D agent/target/transformed/cloud-guest-tool $(DESTDIR)/usr/bin/cloudstack-guest-tool
install -D agent/target/transformed/libvirtqemuhook $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/ install -D agent/target/transformed/libvirtqemuhook $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/
install -D agent/target/transformed/rolling-maintenance $(DESTDIR)/usr/share/$(PACKAGE)-agent/lib/
install -D agent/target/transformed/* $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/agent install -D agent/target/transformed/* $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/agent
# cloudstack-management # cloudstack-management
@ -139,7 +141,7 @@ override_dh_auto_install:
cp -r test/integration/* $(DESTDIR)/usr/share/$(PACKAGE)-integration-tests/ cp -r test/integration/* $(DESTDIR)/usr/share/$(PACKAGE)-integration-tests/
override_dh_systemd_enable: override_dh_systemd_enable:
dh_systemd_enable -pcloudstack-management -pcloudstack-agent -pcloudstack-usage dh_systemd_enable -pcloudstack-management -pcloudstack-agent -pcloudstack-usage -pcloudstack-rolling-maintenance@
override_dh_strip_nondeterminism: override_dh_strip_nondeterminism:
# Disable dh_strip_nondeterminism to speed up the build # Disable dh_strip_nondeterminism to speed up the build

View File

@ -205,4 +205,6 @@ public interface ResourceManager extends ResourceService, Configurable {
HashMap<String, HashMap<String, VgpuTypesInfo>> getGPUStatistics(HostVO host); HashMap<String, HashMap<String, VgpuTypesInfo>> getGPUStatistics(HostVO host);
HostVO findOneRandomRunningHostByHypervisor(HypervisorType type); HostVO findOneRandomRunningHostByHypervisor(HypervisorType type);
boolean cancelMaintenance(final long hostId);
} }

View File

@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
import com.cloud.agent.api.ModifySshKeysCommand; import com.cloud.agent.api.ModifySshKeysCommand;
import com.cloud.agent.api.ModifyStoragePoolCommand; import com.cloud.agent.api.ModifyStoragePoolCommand;
import org.apache.cloudstack.agent.lb.SetupMSListCommand; import org.apache.cloudstack.agent.lb.SetupMSListCommand;
import com.cloud.agent.api.RollingMaintenanceCommand;
import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -117,7 +118,7 @@ public abstract class AgentAttache {
StopCommand.class.toString(), CheckVirtualMachineCommand.class.toString(), PingTestCommand.class.toString(), CheckHealthCommand.class.toString(), StopCommand.class.toString(), CheckVirtualMachineCommand.class.toString(), PingTestCommand.class.toString(), CheckHealthCommand.class.toString(),
ReadyCommand.class.toString(), ShutdownCommand.class.toString(), SetupCommand.class.toString(), ReadyCommand.class.toString(), ShutdownCommand.class.toString(), SetupCommand.class.toString(),
CleanupNetworkRulesCmd.class.toString(), CheckNetworkCommand.class.toString(), PvlanSetupCommand.class.toString(), CheckOnHostCommand.class.toString(), CleanupNetworkRulesCmd.class.toString(), CheckNetworkCommand.class.toString(), PvlanSetupCommand.class.toString(), CheckOnHostCommand.class.toString(),
ModifyTargetsCommand.class.toString(), ModifySshKeysCommand.class.toString(), ModifyStoragePoolCommand.class.toString(), SetupMSListCommand.class.toString()}; ModifyTargetsCommand.class.toString(), ModifySshKeysCommand.class.toString(), ModifyStoragePoolCommand.class.toString(), SetupMSListCommand.class.toString(), RollingMaintenanceCommand.class.toString()};
protected final static String[] s_commandsNotAllowedInConnectingMode = new String[] { StartCommand.class.toString(), CreateCommand.class.toString() }; protected final static String[] s_commandsNotAllowedInConnectingMode = new String[] { StartCommand.class.toString(), CreateCommand.class.toString() };
static { static {
Arrays.sort(s_commandsAllowedInMaintenanceMode); Arrays.sort(s_commandsAllowedInMaintenanceMode);

View File

@ -2997,6 +2997,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
while (true) { while (true) {
try { try {
plan.setMigrationPlan(true);
dest = _dpMgr.planDeployment(profile, plan, excludes, planner); dest = _dpMgr.planDeployment(profile, plan, excludes, planner);
} catch (final AffinityConflictException e2) { } catch (final AffinityConflictException e2) {
s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2); s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2);

View File

@ -111,4 +111,6 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
HostVO findHostInZoneToExecuteCommand(long zoneId, HypervisorType hypervisorType); HostVO findHostInZoneToExecuteCommand(long zoneId, HypervisorType hypervisorType);
List<HostVO> listAllHostsUpByZoneAndHypervisor(long zoneId, HypervisorType hypervisorType); List<HostVO> listAllHostsUpByZoneAndHypervisor(long zoneId, HypervisorType hypervisorType);
List<HostVO> listByClusterAndHypervisorType(long clusterId, HypervisorType hypervisorType);
} }

View File

@ -109,6 +109,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
protected SearchBuilder<HostVO> ClusterStatusSearch; protected SearchBuilder<HostVO> ClusterStatusSearch;
protected SearchBuilder<HostVO> TypeNameZoneSearch; protected SearchBuilder<HostVO> TypeNameZoneSearch;
protected SearchBuilder<HostVO> AvailHypevisorInZone; protected SearchBuilder<HostVO> AvailHypevisorInZone;
protected SearchBuilder<HostVO> ClusterHypervisorSearch;
protected SearchBuilder<HostVO> DirectConnectSearch; protected SearchBuilder<HostVO> DirectConnectSearch;
protected SearchBuilder<HostVO> ManagedDirectConnectSearch; protected SearchBuilder<HostVO> ManagedDirectConnectSearch;
@ -293,6 +294,13 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
DirectlyConnectedSearch.and("resourceState", DirectlyConnectedSearch.entity().getResourceState(), SearchCriteria.Op.NOTIN); DirectlyConnectedSearch.and("resourceState", DirectlyConnectedSearch.entity().getResourceState(), SearchCriteria.Op.NOTIN);
DirectlyConnectedSearch.done(); DirectlyConnectedSearch.done();
ClusterHypervisorSearch = createSearchBuilder();
ClusterHypervisorSearch.and("clusterId", ClusterHypervisorSearch.entity().getClusterId(), SearchCriteria.Op.EQ);
ClusterHypervisorSearch.and("hypervisor", ClusterHypervisorSearch.entity().getHypervisorType(), SearchCriteria.Op.EQ);
ClusterHypervisorSearch.and("type", ClusterHypervisorSearch.entity().getType(), SearchCriteria.Op.EQ);
ClusterHypervisorSearch.and("status", ClusterHypervisorSearch.entity().getStatus(), SearchCriteria.Op.EQ);
ClusterHypervisorSearch.done();
UnmanagedDirectConnectSearch = createSearchBuilder(); UnmanagedDirectConnectSearch = createSearchBuilder();
UnmanagedDirectConnectSearch.and("resource", UnmanagedDirectConnectSearch.entity().getResource(), SearchCriteria.Op.NNULL); UnmanagedDirectConnectSearch.and("resource", UnmanagedDirectConnectSearch.entity().getResource(), SearchCriteria.Op.NNULL);
UnmanagedDirectConnectSearch.and("server", UnmanagedDirectConnectSearch.entity().getManagementServerId(), SearchCriteria.Op.NULL); UnmanagedDirectConnectSearch.and("server", UnmanagedDirectConnectSearch.entity().getManagementServerId(), SearchCriteria.Op.NULL);
@ -1213,6 +1221,16 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override
public List<HostVO> listByClusterAndHypervisorType(long clusterId, HypervisorType hypervisorType) {
SearchCriteria<HostVO> sc = ClusterHypervisorSearch.create();
sc.setParameters("clusterId", clusterId);
sc.setParameters("hypervisor", hypervisorType);
sc.setParameters("type", Type.Routing);
sc.setParameters("status", Status.Up);
return listBy(sc);
}
private ResultSet executeSqlGetResultsetForMethodFindHostInZoneToExecuteCommand(HypervisorType hypervisorType, long zoneId, TransactionLegacy tx, String sql) throws SQLException { private ResultSet executeSqlGetResultsetForMethodFindHostInZoneToExecuteCommand(HypervisorType hypervisorType, long zoneId, TransactionLegacy tx, String sql) throws SQLException {
PreparedStatement pstmt = tx.prepareAutoCloseStatement(sql); PreparedStatement pstmt = tx.prepareAutoCloseStatement(sql);
pstmt.setString(1, Objects.toString(hypervisorType)); pstmt.setString(1, Objects.toString(hypervisorType));

View File

@ -292,6 +292,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins
install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service
install -D packaging/systemd/cloudstack-rolling-maintenance@.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-rolling-maintenance@.service
install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent
install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties
install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties
@ -300,6 +301,7 @@ install -D agent/target/transformed/cloud-setup-agent ${RPM_BUILD_ROOT}%{_bindir
install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade
install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool
install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook
install -D agent/target/transformed/rolling-maintenance ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/rolling-maintenance
install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh
install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh
install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
@ -428,6 +430,7 @@ cp -a ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook %{_sysconfd
mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp
/sbin/service libvirtd restart /sbin/service libvirtd restart
/sbin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true /sbin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true
/sbin/systemctl enable cloudstack-rolling-maintenance@p > /dev/null 2>&1 || true
# if saved configs from upgrade exist, copy them over # if saved configs from upgrade exist, copy them over
if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then
@ -519,6 +522,7 @@ pip install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz
%attr(0755,root,root) %{_bindir}/%{name}-guest-tool %attr(0755,root,root) %{_bindir}/%{name}-guest-tool
%attr(0755,root,root) %{_bindir}/%{name}-ssh %attr(0755,root,root) %{_bindir}/%{name}-ssh
%attr(0644,root,root) %{_unitdir}/%{name}-agent.service %attr(0644,root,root) %{_unitdir}/%{name}-agent.service
%attr(0644,root,root) %{_unitdir}/%{name}-rolling-maintenance@.service
%config(noreplace) %{_sysconfdir}/default/%{name}-agent %config(noreplace) %{_sysconfdir}/default/%{name}-agent
%attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh %attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh
%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent %config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent
@ -527,6 +531,7 @@ pip install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz
%dir %{_localstatedir}/log/%{name}/agent %dir %{_localstatedir}/log/%{name}/agent
%attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar %attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar
%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook %attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook
%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/rolling-maintenance
%dir %{_datadir}/%{name}-agent/plugins %dir %{_datadir}/%{name}-agent/plugins
%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE %{_defaultdocdir}/%{name}-agent-%{version}/LICENSE
%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE %{_defaultdocdir}/%{name}-agent-%{version}/NOTICE

View File

@ -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.
# Systemd unit file for CloudStack Rolling Maintenance
[Unit]
Description=Rolling maintenance executor %I
After=network.target local-fs.target
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
WorkingDirectory=/usr/share/cloudstack-agent/lib/
ExecStart=/usr/share/cloudstack-agent/lib/rolling-maintenance %I
Restart=no

View File

@ -46,6 +46,9 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceAgentExecutor;
import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceExecutor;
import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceServiceExecutor;
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;
@ -276,6 +279,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
protected int _migrateDowntime; protected int _migrateDowntime;
protected int _migratePauseAfter; protected int _migratePauseAfter;
protected boolean _diskActivityCheckEnabled; protected boolean _diskActivityCheckEnabled;
protected RollingMaintenanceExecutor rollingMaintenanceExecutor;
protected long _diskActivityCheckFileSizeMin = 10485760; // 10MB protected long _diskActivityCheckFileSizeMin = 10485760; // 10MB
protected int _diskActivityCheckTimeoutSeconds = 120; // 120s protected int _diskActivityCheckTimeoutSeconds = 120; // 120s
protected long _diskActivityInactiveThresholdMilliseconds = 30000; // 30s protected long _diskActivityInactiveThresholdMilliseconds = 30000; // 30s
@ -426,6 +430,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return _migrateSpeed; return _migrateSpeed;
} }
public RollingMaintenanceExecutor getRollingMaintenanceExecutor() {
return rollingMaintenanceExecutor;
}
public String getPingTestPath() { public String getPingTestPath() {
return _pingTestPath; return _pingTestPath;
} }
@ -790,6 +798,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
_hypervisorType = HypervisorType.KVM; _hypervisorType = HypervisorType.KVM;
} }
String hooksDir = (String)params.get("rolling.maintenance.hooks.dir");
value = (String) params.get("rolling.maintenance.service.executor.disabled");
rollingMaintenanceExecutor = Boolean.parseBoolean(value) ? new RollingMaintenanceAgentExecutor(hooksDir) :
new RollingMaintenanceServiceExecutor(hooksDir);
_hypervisorURI = (String)params.get("hypervisor.uri"); _hypervisorURI = (String)params.get("hypervisor.uri");
if (_hypervisorURI == null) { if (_hypervisorURI == null) {
_hypervisorURI = LibvirtConnection.getHypervisorURI(_hypervisorType.toString()); _hypervisorURI = LibvirtConnection.getHypervisorURI(_hypervisorType.toString());

View File

@ -0,0 +1,88 @@
// 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.rolling.maintenance;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.google.common.base.Strings;
import org.apache.log4j.Logger;
import org.joda.time.Duration;
import java.io.File;
public class RollingMaintenanceAgentExecutor extends RollingMaintenanceExecutorBase implements RollingMaintenanceExecutor {
private static final Logger s_logger = Logger.getLogger(RollingMaintenanceAgentExecutor.class);
private String output;
private boolean success;
public RollingMaintenanceAgentExecutor(String hooksDir) {
super(hooksDir);
}
@Override
public Pair<Boolean, String> startStageExecution(String stage, File scriptFile, int timeout, String payload) {
checkHooksDirectory();
Duration duration = Duration.standardSeconds(timeout);
final Script script = new Script(scriptFile.getAbsolutePath(), duration, s_logger);
final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
if (!Strings.isNullOrEmpty(payload)) {
script.add(payload);
}
s_logger.info("Executing stage: " + stage + " script: " + script);
output = script.execute(parser) + " " + parser.getLines();
if (script.isTimeout()) {
String msg = "Script " + scriptFile + " timed out";
s_logger.error(msg);
success = false;
return new Pair<>(false, msg);
}
int exitValue = script.getExitValue();
if (exitValue == exitValueTerminatedSignal) {
throw new CloudRuntimeException("Script " + scriptFile + " terminated");
}
success = exitValue == 0 || exitValue == exitValueAvoidMaintenance;
setAvoidMaintenance(exitValue == exitValueAvoidMaintenance);
s_logger.info("Execution finished for stage: " + stage + " script: " + script + ": " + exitValue);
if (s_logger.isDebugEnabled()) {
s_logger.debug(output);
s_logger.debug("Stage " + stage + " execution finished: " + exitValue);
}
return new Pair<>(true, "Stage " + stage + " finished");
}
@Override
public String getStageExecutionOutput(String stage, File scriptFile) {
return output;
}
@Override
public boolean isStageRunning(String stage, File scriptFile, String payload) {
// In case of reconnection, it is assumed that the stage is finished
return false;
}
@Override
public boolean getStageExecutionSuccess(String stage, File scriptFile) {
return success;
}
}

View File

@ -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 com.cloud.hypervisor.kvm.resource.rolling.maintenance;
import com.cloud.utils.Pair;
import java.io.File;
public interface RollingMaintenanceExecutor {
File getStageScriptFile(String stage);
Pair<Boolean, String> startStageExecution(String stage, File scriptFile, int timeout, String payload);
String getStageExecutionOutput(String stage, File scriptFile);
boolean isStageRunning(String stage, File scriptFile, String payload);
boolean getStageExecutionSuccess(String stage, File scriptFile);
boolean getStageAvoidMaintenance(String stage, File scriptFile);
}

View File

@ -0,0 +1,91 @@
// 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.rolling.maintenance;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import java.io.File;
public abstract class RollingMaintenanceExecutorBase implements RollingMaintenanceExecutor {
private String hooksDir;
private int timeout;
private boolean avoidMaintenance = false;
static final int exitValueAvoidMaintenance = 70;
static final int exitValueTerminatedSignal = 143;
private static final Logger s_logger = Logger.getLogger(RollingMaintenanceExecutor.class);
void setTimeout(int timeout) {
this.timeout = timeout;
}
long getTimeout() {
return timeout;
}
private void sanitizeHoooksDirFormat() {
if (StringUtils.isNotBlank(this.hooksDir) && !this.hooksDir.endsWith("/")) {
this.hooksDir += "/";
}
}
RollingMaintenanceExecutorBase(String hooksDir) {
this.hooksDir = hooksDir;
sanitizeHoooksDirFormat();
}
protected boolean existsAndIsFile(String filepath) {
File file = new File(filepath);
return file.exists() && file.isFile();
}
public File getStageScriptFile(String stage) {
String scriptPath = hooksDir + stage;
if (existsAndIsFile(scriptPath)) {
return new File(scriptPath);
} else if (existsAndIsFile(scriptPath + ".sh")) {
return new File(scriptPath + ".sh");
} else if (existsAndIsFile(scriptPath + ".py")) {
return new File(scriptPath + ".py");
} else {
String msg = "Unable to locate script for stage: " + stage + " in directory: " + hooksDir;
s_logger.warn(msg);
return null;
}
}
void checkHooksDirectory() {
if (StringUtils.isBlank(hooksDir)) {
throw new CloudRuntimeException("Hooks directory is empty, please specify it on agent.properties and restart the agent");
}
}
String getHooksDir() {
return hooksDir;
}
public void setAvoidMaintenance(boolean avoidMaintenance) {
this.avoidMaintenance = avoidMaintenance;
}
public boolean getStageAvoidMaintenance(String stage, File scriptFile) {
return avoidMaintenance;
}
}

View File

@ -0,0 +1,137 @@
// 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.rolling.maintenance;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class RollingMaintenanceServiceExecutor extends RollingMaintenanceExecutorBase implements RollingMaintenanceExecutor {
private static final String servicePrefix = "cloudstack-rolling-maintenance";
private static final String resultsFileSuffix = "rolling-maintenance-results";
private static final String outputFileSuffix = "rolling-maintenance-output";
private static final Logger s_logger = Logger.getLogger(RollingMaintenanceServiceExecutor.class);
public RollingMaintenanceServiceExecutor(String hooksDir) {
super(hooksDir);
}
/**
* Generate and return escaped instance name to use on systemd service invokation
*/
private String generateInstanceName(String stage, String file, String payload) {
String instanceName = String.format("%s,%s,%s,%s,%s", stage, file, getTimeout(),
getResultsFilePath(), getOutputFilePath());
if (StringUtils.isNotBlank(payload)) {
instanceName += "," + payload;
}
return Script.runSimpleBashScript(String.format("systemd-escape '%s'", instanceName));
}
private String invokeService(String action, String stage, String file, String payload) {
s_logger.debug("Invoking rolling maintenance service for stage: " + stage + " and file " + file + " with action: " + action);
final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
Script command = new Script("/bin/systemctl", s_logger);
command.add(action);
String service = servicePrefix + "@" + generateInstanceName(stage, file, payload);
command.add(service);
String result = command.execute(parser);
int exitValue = command.getExitValue();
s_logger.trace("Execution: " + command.toString() + " - exit code: " + exitValue +
": " + result + (StringUtils.isNotBlank(parser.getLines()) ? parser.getLines() : ""));
return StringUtils.isBlank(result) ? parser.getLines().replace("\n", " ") : result;
}
@Override
public Pair<Boolean, String> startStageExecution(String stage, File scriptFile, int timeout, String payload) {
checkHooksDirectory();
setTimeout(timeout);
String result = invokeService("start", stage, scriptFile.getAbsolutePath(), payload);
if (StringUtils.isNotBlank(result)) {
throw new CloudRuntimeException("Error starting stage: " + stage + " execution: " + result);
}
s_logger.trace("Stage " + stage + "execution started");
return new Pair<>(true, "OK");
}
private String getResultsFilePath() {
return getHooksDir() + resultsFileSuffix;
}
private String getOutputFilePath() {
return getHooksDir() + outputFileSuffix;
}
private String readFromFile(String filePath) {
StringBuilder contentBuilder = new StringBuilder();
try (Stream<String> stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8)) {
stream.forEach(s -> contentBuilder.append(s).append("\n"));
} catch (IOException e) {
e.printStackTrace();
}
return contentBuilder.toString();
}
@Override
public String getStageExecutionOutput(String stage, File scriptFile) {
return readFromFile(getOutputFilePath());
}
@Override
public boolean isStageRunning(String stage, File scriptFile, String payload) {
String result = invokeService("is-active", stage, scriptFile.getAbsolutePath(), payload);
if (StringUtils.isNotBlank(result) && result.equals("failed")) {
String status = invokeService("status", stage, scriptFile.getAbsolutePath(), payload);
String errorMsg = "Stage " + stage + " execution failed, status: " + status;
s_logger.error(errorMsg);
throw new CloudRuntimeException(errorMsg);
}
return StringUtils.isNotBlank(result) && result.equals("active");
}
@Override
public boolean getStageExecutionSuccess(String stage, File scriptFile) {
String fileContent = readFromFile(getResultsFilePath());
if (StringUtils.isBlank(fileContent)) {
throw new CloudRuntimeException("Empty content in file " + getResultsFilePath());
}
fileContent = fileContent.replace("\n", "");
String[] parts = fileContent.split(",");
if (parts.length < 3) {
throw new CloudRuntimeException("Results file " + getResultsFilePath() + " unexpected content: " + fileContent);
}
if (!parts[0].equalsIgnoreCase(stage)) {
throw new CloudRuntimeException("Expected stage " + stage + " results but got stage " + parts[0]);
}
setAvoidMaintenance(Boolean.parseBoolean(parts[2]));
return Boolean.parseBoolean(parts[1]);
}
}

View File

@ -0,0 +1,81 @@
//
// 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.RollingMaintenanceAnswer;
import com.cloud.agent.api.RollingMaintenanceCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceAgentExecutor;
import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceExecutor;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.resource.RollingMaintenanceManager;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.log4j.Logger;
import java.io.File;
@ResourceWrapper(handles = RollingMaintenanceCommand.class)
public class LibvirtRollingMaintenanceCommandWrapper extends CommandWrapper<RollingMaintenanceCommand, RollingMaintenanceAnswer, LibvirtComputingResource> {
private static final Logger s_logger = Logger.getLogger(LibvirtRollingMaintenanceCommandWrapper.class);
@Override
public RollingMaintenanceAnswer execute(RollingMaintenanceCommand command, LibvirtComputingResource resource) {
RollingMaintenanceExecutor executor = resource.getRollingMaintenanceExecutor();
String stage = command.isCheckMaintenanceScript() ? RollingMaintenanceManager.Stage.Maintenance.toString() : command.getStage();
int timeout = command.getWait();
String payload = command.getPayload();
try {
File scriptFile = executor.getStageScriptFile(stage);
if (command.isCheckMaintenanceScript()) {
return new RollingMaintenanceAnswer(command, scriptFile != null);
} else if (scriptFile == null) {
s_logger.info("No script file defined for stage " + stage + ". Skipping stage...");
return new RollingMaintenanceAnswer(command, true, "Skipped stage " + stage, true);
}
if (command.isStarted() && executor instanceof RollingMaintenanceAgentExecutor) {
String msg = "Stage has been started previously and the agent restarted, setting stage as finished";
s_logger.info(msg);
return new RollingMaintenanceAnswer(command, true, msg, true);
}
s_logger.info("Processing stage " + stage);
if (!command.isStarted()) {
executor.startStageExecution(stage, scriptFile, timeout, payload);
}
if (executor.isStageRunning(stage, scriptFile, payload)) {
return new RollingMaintenanceAnswer(command, true, "Stage " + stage + " still running", false);
}
boolean success = executor.getStageExecutionSuccess(stage, scriptFile);
String output = executor.getStageExecutionOutput(stage, scriptFile);
RollingMaintenanceAnswer answer = new RollingMaintenanceAnswer(command, success, output, true);
if (executor.getStageAvoidMaintenance(stage, scriptFile)) {
s_logger.info("Avoid maintenance flag added to the answer for the stage " + stage);
answer.setAvoidMaintenance(true);
}
s_logger.info("Finished processing stage " + stage);
return answer;
} catch (CloudRuntimeException e) {
return new RollingMaintenanceAnswer(command, false, e.getMessage(), false);
}
}
}

View File

@ -31,6 +31,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.resource.RollingMaintenanceManager;
import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
@ -44,6 +45,9 @@ import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd;
import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse;
import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AsyncJobResponse;
import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse;
@ -4281,4 +4285,31 @@ public class ApiResponseHelper implements ResponseGenerator {
} }
return responses; return responses;
} }
@Override
public RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List<RollingMaintenanceManager.HostUpdated> hostsUpdated, List<RollingMaintenanceManager.HostSkipped> hostsSkipped) {
RollingMaintenanceResponse response = new RollingMaintenanceResponse(success, details);
List<RollingMaintenanceHostUpdatedResponse> updated = new ArrayList<>();
for (RollingMaintenanceManager.HostUpdated h : hostsUpdated) {
RollingMaintenanceHostUpdatedResponse r = new RollingMaintenanceHostUpdatedResponse();
r.setHostId(h.getHost().getUuid());
r.setHostName(h.getHost().getName());
r.setStartDate(getDateStringInternal(h.getStart()));
r.setEndDate(getDateStringInternal(h.getEnd()));
r.setOutput(h.getOutputMsg());
updated.add(r);
}
List<RollingMaintenanceHostSkippedResponse> skipped = new ArrayList<>();
for (RollingMaintenanceManager.HostSkipped h : hostsSkipped) {
RollingMaintenanceHostSkippedResponse r = new RollingMaintenanceHostSkippedResponse();
r.setHostId(h.getHost().getUuid());
r.setHostName(h.getHost().getName());
r.setReason(h.getReason());
skipped.add(r);
}
response.setUpdatedHosts(updated);
response.setSkippedHosts(skipped);
response.setObjectName("rollingmaintenance");
return response;
}
} }

View File

@ -1041,7 +1041,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> {
for (Long clusterId : clusterList) { for (Long clusterId : clusterList) {
ClusterVO clusterVO = _clusterDao.findById(clusterId); ClusterVO clusterVO = _clusterDao.findById(clusterId);
if (clusterVO.getAllocationState() == Grouping.AllocationState.Disabled) { if (clusterVO.getAllocationState() == Grouping.AllocationState.Disabled && !plan.isMigrationPlan()) {
s_logger.debug("Cannot deploy in disabled cluster " + clusterId + ", skipping this cluster"); s_logger.debug("Cannot deploy in disabled cluster " + clusterId + ", skipping this cluster");
avoid.addCluster(clusterVO.getId()); avoid.addCluster(clusterVO.getId());
} }

View File

@ -2485,7 +2485,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
} }
} }
private boolean cancelMaintenance(final long hostId) { public boolean cancelMaintenance(final long hostId) {
try { try {
final Boolean result = propagateResourceEvent(hostId, ResourceState.Event.AdminCancelMaintenance); final Boolean result = propagateResourceEvent(hostId, ResourceState.Event.AdminCancelMaintenance);

View File

@ -0,0 +1,734 @@
// 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.resource;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.RollingMaintenanceAnswer;
import com.cloud.agent.api.RollingMaintenanceCommand;
import com.cloud.alert.AlertManager;
import com.cloud.capacity.CapacityManager;
import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.ClusterDetailsVO;
import com.cloud.deploy.DeployDestination;
import com.cloud.event.ActionEventUtils;
import com.cloud.event.EventVO;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostTagsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.org.Cluster;
import com.cloud.org.Grouping;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineProfileImpl;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.affinity.AffinityGroupProcessor;
import org.apache.cloudstack.api.command.admin.host.PrepareForMaintenanceCmd;
import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class RollingMaintenanceManagerImpl extends ManagerBase implements RollingMaintenanceManager {
@Inject
private HostDao hostDao;
@Inject
private AgentManager agentManager;
@Inject
private ResourceManager resourceManager;
@Inject
private CapacityManager capacityManager;
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private ServiceOfferingDao serviceOfferingDao;
@Inject
private ClusterDetailsDao clusterDetailsDao;
@Inject
private HostTagsDao hostTagsDao;
@Inject
private AlertManager alertManager;
protected List<AffinityGroupProcessor> _affinityProcessors;
public void setAffinityGroupProcessors(List<AffinityGroupProcessor> affinityProcessors) {
_affinityProcessors = affinityProcessors;
}
public static final Logger s_logger = Logger.getLogger(RollingMaintenanceManagerImpl.class.getName());
private Pair<ResourceType, List<Long>> getResourceTypeAndIdPair(List<Long> podIds, List<Long> clusterIds, List<Long> zoneIds, List<Long> hostIds) {
Pair<ResourceType, List<Long>> pair = CollectionUtils.isNotEmpty(podIds) ? new Pair<>(ResourceType.Pod, podIds) :
CollectionUtils.isNotEmpty(clusterIds) ? new Pair<>(ResourceType.Cluster, clusterIds) :
CollectionUtils.isNotEmpty(zoneIds) ? new Pair<>(ResourceType.Zone, zoneIds) :
CollectionUtils.isNotEmpty(hostIds) ? new Pair<>(ResourceType.Host, hostIds) : null;
if (pair == null) {
throw new CloudRuntimeException("Parameters podId, clusterId, zoneId, hostId are mutually exclusive, " +
"please set only one of them");
}
return pair;
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
return true;
}
private void updateCluster(long clusterId, String state) {
Cluster cluster = resourceManager.getCluster(clusterId);
if (cluster == null) {
throw new InvalidParameterValueException("Unable to find the cluster by id=" + clusterId);
}
resourceManager.updateCluster(cluster, "", "", state, "");
}
private void generateReportAndFinishingEvent(StartRollingMaintenanceCmd cmd, boolean success, String details,
List<HostUpdated> hostsUpdated, List<HostSkipped> hostsSkipped) {
Pair<ResourceType, List<Long>> pair = getResourceTypeIdPair(cmd);
ResourceType entity = pair.first();
List<Long> ids = pair.second();
String description = String.format("Success: %s, details: %s, hosts updated: %s, hosts skipped: %s", success, details,
generateReportHostsUpdated(hostsUpdated), generateReportHostsSkipped(hostsSkipped));
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), CallContext.current().getCallingAccountId(),
EventVO.LEVEL_INFO, cmd.getEventType(),
"Completed rolling maintenance for entity " + entity + " with IDs: " + ids + " - " + description, 0);
}
private String generateReportHostsUpdated(List<HostUpdated> hostsUpdated) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(hostsUpdated.size());
return stringBuilder.toString();
}
private String generateReportHostsSkipped(List<HostSkipped> hostsSkipped) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(hostsSkipped.size());
return stringBuilder.toString();
}
@Override
public Ternary<Boolean, String, Pair<List<HostUpdated>, List<HostSkipped>>> startRollingMaintenance(StartRollingMaintenanceCmd cmd) {
Pair<ResourceType, List<Long>> pair = getResourceTypeAndIdPair(cmd.getPodIds(), cmd.getClusterIds(), cmd.getZoneIds(), cmd.getHostIds());
ResourceType type = pair.first();
List<Long> ids = pair.second();
int timeout = cmd.getTimeout() == null ? KvmRollingMaintenanceStageTimeout.value() : cmd.getTimeout();
String payload = cmd.getPayload();
Boolean forced = cmd.getForced();
Set<Long> disabledClusters = new HashSet<>();
Map<Long, String> hostsToAvoidMaintenance = new HashMap<>();
boolean success = false;
String details = null;
List<HostUpdated> hostsUpdated = new ArrayList<>();
List<HostSkipped> hostsSkipped = new ArrayList<>();
if (timeout <= KvmRollingMaintenancePingInterval.value()) {
return new Ternary<>(success, "The timeout value provided must be greater or equal than the ping interval " +
"defined in '" + KvmRollingMaintenancePingInterval.key() + "'", new Pair<>(hostsUpdated, hostsSkipped));
}
try {
Map<Long, List<Host>> hostsByCluster = getHostsByClusterForRollingMaintenance(type, ids);
for (Long clusterId : hostsByCluster.keySet()) {
Cluster cluster = resourceManager.getCluster(clusterId);
List<Host> hosts = hostsByCluster.get(clusterId);
if (!isMaintenanceAllowedByVMStates(cluster, hosts, hostsSkipped)) {
if (forced) {
continue;
}
success = false;
details = "VMs in invalid states in cluster: " + cluster.getUuid();
return new Ternary<>(success, details, new Pair<>(hostsUpdated, hostsSkipped));
}
disableClusterIfEnabled(cluster, disabledClusters);
s_logger.debug("State checks on the hosts in the cluster");
performStateChecks(cluster, hosts, forced, hostsSkipped);
s_logger.debug("Checking hosts capacity before attempting rolling maintenance");
performCapacityChecks(cluster, hosts, forced);
s_logger.debug("Attempting pre-flight stages on each host before starting rolling maintenance");
performPreFlightChecks(hosts, timeout, payload, forced, hostsToAvoidMaintenance);
for (Host host: hosts) {
Ternary<Boolean, Boolean, String> hostResult = startRollingMaintenanceHostInCluster(cluster, host,
timeout, payload, forced, hostsToAvoidMaintenance, hostsUpdated, hostsSkipped);
if (hostResult.second()) {
continue;
}
if (hostResult.first()) {
success = false;
details = hostResult.third();
return new Ternary<>(success, details, new Pair<>(hostsUpdated, hostsSkipped));
}
}
enableClusterIfDisabled(cluster, disabledClusters);
}
} catch (AgentUnavailableException | InterruptedException | CloudRuntimeException e) {
String err = "Error starting rolling maintenance: " + e.getMessage();
s_logger.error(err, e);
success = false;
details = err;
return new Ternary<>(success, details, new Pair<>(hostsUpdated, hostsSkipped));
} finally {
// Enable back disabled clusters
for (Long clusterId : disabledClusters) {
Cluster cluster = resourceManager.getCluster(clusterId);
if (cluster.getAllocationState() == Grouping.AllocationState.Disabled) {
updateCluster(clusterId, "Enabled");
}
}
generateReportAndFinishingEvent(cmd, success, details, hostsUpdated, hostsSkipped);
}
success = true;
details = "OK";
return new Ternary<>(success, details, new Pair<>(hostsUpdated, hostsSkipped));
}
/**
* Perform state checks on the hosts in a cluster
*/
protected void performStateChecks(Cluster cluster, List<Host> hosts, Boolean forced, List<HostSkipped> hostsSkipped) {
List<Host> hostsToDrop = new ArrayList<>();
for (Host host : hosts) {
if (host.getStatus() != Status.Up) {
String msg = "Host " + host.getUuid() + " is not connected, state = " + host.getStatus().toString();
if (forced) {
hostsSkipped.add(new HostSkipped(host, msg));
hostsToDrop.add(host);
continue;
}
throw new CloudRuntimeException(msg);
}
if (host.getResourceState() != ResourceState.Enabled) {
String msg = "Host " + host.getUuid() + " is not enabled, state = " + host.getResourceState().toString();
if (forced) {
hostsSkipped.add(new HostSkipped(host, msg));
hostsToDrop.add(host);
continue;
}
throw new CloudRuntimeException(msg);
}
}
if (CollectionUtils.isNotEmpty(hostsToDrop)) {
hosts.removeAll(hostsToDrop);
}
}
/**
* Do not allow rolling maintenance if there are VMs in Starting/Stopping/Migrating/Error/Unknown state
*/
private boolean isMaintenanceAllowedByVMStates(Cluster cluster, List<Host> hosts, List<HostSkipped> hostsSkipped) {
for (Host host : hosts) {
List<VMInstanceVO> notAllowedStates = vmInstanceDao.findByHostInStates(host.getId(), State.Starting, State.Stopping,
State.Migrating, State.Error, State.Unknown);
if (notAllowedStates.size() > 0) {
String msg = "There are VMs in starting/stopping/migrating/error/unknown state, not allowing rolling maintenance in the cluster";
HostSkipped skipped = new HostSkipped(host, msg);
hostsSkipped.add(skipped);
return false;
}
}
return true;
}
/**
* Start rolling maintenance for a single host
* @return tuple: (FAIL, SKIP, DETAILS), where:
* - FAIL: True if rolling maintenance must fail
* - SKIP: True if host must be skipped
* - DETAILS: Information retrieved by the host
*/
private Ternary<Boolean, Boolean, String> startRollingMaintenanceHostInCluster(Cluster cluster, Host host, int timeout,
String payload, Boolean forced,
Map<Long, String> hostsToAvoidMaintenance,
List<HostUpdated> hostsUpdated,
List<HostSkipped> hostsSkipped) throws InterruptedException, AgentUnavailableException {
Ternary<Boolean, Boolean, String> result;
if (!isMaintenanceScriptDefinedOnHost(host, hostsSkipped)) {
String msg = "There is no maintenance script on the host";
hostsSkipped.add(new HostSkipped(host, msg));
return new Ternary<>(false, true, msg);
}
result = performPreMaintenanceStageOnHost(host, timeout, payload, forced, hostsToAvoidMaintenance, hostsSkipped);
if (result.first() || result.second()) {
return result;
}
if (isMaintenanceStageAvoided(host, hostsToAvoidMaintenance, hostsSkipped)) {
return new Ternary<>(false, true, "Maintenance stage must be avoided");
}
s_logger.debug("Updating capacity before re-checking capacity");
alertManager.recalculateCapacity();
result = reCheckCapacityBeforeMaintenanceOnHost(cluster, host, forced, hostsSkipped);
if (result.first() || result.second()) {
return result;
}
Date startTime = new Date();
putHostIntoMaintenance(host);
result = performMaintenanceStageOnHost(host, timeout, payload, forced, hostsToAvoidMaintenance, hostsSkipped);
if (result.first() || result.second()) {
cancelHostMaintenance(host);
return result;
}
cancelHostMaintenance(host);
Date endTime = new Date();
HostUpdated hostUpdated = new HostUpdated(host, startTime, endTime, result.third());
hostsUpdated.add(hostUpdated);
result = performPostMaintenanceStageOnHost(host, timeout, payload, forced, hostsToAvoidMaintenance, hostsSkipped);
if (result.first() || result.second()) {
return result;
}
return new Ternary<>(false, false, "Completed rolling maintenance on host " + host.getUuid());
}
/**
* Perform Post-Maintenance stage on host
* @return tuple: (FAIL, SKIP, DETAILS), where:
* - FAIL: True if rolling maintenance must fail
* - SKIP: True if host must be skipped
* - DETAILS: Information retrieved by the host after executing the stage
* @throws InterruptedException
*/
private Ternary<Boolean, Boolean, String> performPostMaintenanceStageOnHost(Host host, int timeout, String payload, Boolean forced, Map<Long, String> hostsToAvoidMaintenance, List<HostSkipped> hostsSkipped) throws InterruptedException {
Ternary<Boolean, String, Boolean> result = performStageOnHost(host, Stage.PostMaintenance, timeout, payload, forced);
if (!result.first()) {
if (forced) {
String msg = "Post-maintenance script failed: " + result.second();
hostsSkipped.add(new HostSkipped(host, msg));
return new Ternary<>(true, true, msg);
}
return new Ternary<>(true, false, result.second());
}
return new Ternary<>(false, false, result.second());
}
/**
* Cancel maintenance mode on host
* @param host host
*/
private void cancelHostMaintenance(Host host) {
if (!resourceManager.cancelMaintenance(host.getId())) {
String message = "Could not cancel maintenance on host " + host.getUuid();
s_logger.error(message);
throw new CloudRuntimeException(message);
}
}
/**
* Perform Maintenance stage on host
* @return tuple: (FAIL, SKIP, DETAILS), where:
* - FAIL: True if rolling maintenance must fail
* - SKIP: True if host must be skipped
* - DETAILS: Information retrieved by the host after executing the stage
* @throws InterruptedException
*/
private Ternary<Boolean, Boolean, String> performMaintenanceStageOnHost(Host host, int timeout, String payload, Boolean forced, Map<Long, String> hostsToAvoidMaintenance, List<HostSkipped> hostsSkipped) throws InterruptedException {
Ternary<Boolean, String, Boolean> result = performStageOnHost(host, Stage.Maintenance, timeout, payload, forced);
if (!result.first()) {
if (forced) {
String msg = "Maintenance script failed: " + result.second();
hostsSkipped.add(new HostSkipped(host, msg));
return new Ternary<>(true, true, msg);
}
return new Ternary<>(true, false, result.second());
}
return new Ternary<>(false, false, result.second());
}
/**
* Puts host into maintenance and waits for its completion
* @param host host
* @throws InterruptedException
* @throws AgentUnavailableException
*/
private void putHostIntoMaintenance(Host host) throws InterruptedException, AgentUnavailableException {
s_logger.debug("Trying to set the host " + host.getId() + " into maintenance");
PrepareForMaintenanceCmd cmd = new PrepareForMaintenanceCmd();
cmd.setId(host.getId());
resourceManager.maintain(cmd);
waitForHostInMaintenance(host.getId());
}
/**
* Enable back disabled cluster
* @param cluster cluster to enable if it has been disabled
* @param disabledClusters set of disabled clusters
*/
private void enableClusterIfDisabled(Cluster cluster, Set<Long> disabledClusters) {
if (cluster.getAllocationState() == Grouping.AllocationState.Disabled && disabledClusters.contains(cluster.getId())) {
updateCluster(cluster.getId(), "Enabled");
}
}
/**
* Re-check capacity to ensure the host can transit into maintenance state
* @return tuple: (FAIL, SKIP, DETAILS), where:
* - FAIL: True if rolling maintenance must fail
* - SKIP: True if host must be skipped
* - DETAILS: Information retrieved after capacity checks
*/
private Ternary<Boolean, Boolean, String> reCheckCapacityBeforeMaintenanceOnHost(Cluster cluster, Host host, Boolean forced, List<HostSkipped> hostsSkipped) {
Pair<Boolean, String> capacityCheckBeforeMaintenance = performCapacityChecksBeforeHostInMaintenance(host, cluster);
if (!capacityCheckBeforeMaintenance.first()) {
String errorMsg = "Capacity check failed for host " + host.getUuid() + ": " + capacityCheckBeforeMaintenance.second();
if (forced) {
s_logger.info("Skipping host " + host.getUuid() + " as: " + errorMsg);
hostsSkipped.add(new HostSkipped(host, errorMsg));
return new Ternary<>(true, true, capacityCheckBeforeMaintenance.second());
}
return new Ternary<>(true, false, capacityCheckBeforeMaintenance.second());
}
return new Ternary<>(false, false, capacityCheckBeforeMaintenance.second());
}
/**
* Indicates if the maintenance stage must be avoided
*/
private boolean isMaintenanceStageAvoided(Host host, Map<Long, String> hostsToAvoidMaintenance, List<HostSkipped> hostsSkipped) {
if (hostsToAvoidMaintenance.containsKey(host.getId())) {
s_logger.debug("Host " + host.getId() + " is not being put into maintenance, skipping it");
HostSkipped hostSkipped = new HostSkipped(host, hostsToAvoidMaintenance.get(host.getId()));
hostsSkipped.add(hostSkipped);
return true;
}
return false;
}
/**
* Perform Pre-Maintenance stage on host
* @return tuple: (FAIL, SKIP, DETAILS), where:
* - FAIL: True if rolling maintenance must fail
* - SKIP: True if host must be skipped
* - DETAILS: Information retrieved by the host after executing the stage
* @throws InterruptedException
*/
private Ternary<Boolean, Boolean, String> performPreMaintenanceStageOnHost(Host host, int timeout, String payload, Boolean forced,
Map<Long, String> hostsToAvoidMaintenance,
List<HostSkipped> hostsSkipped) throws InterruptedException {
Ternary<Boolean, String, Boolean> result = performStageOnHost(host, Stage.PreMaintenance, timeout, payload, forced);
if (!result.first()) {
if (forced) {
String msg = "Pre-maintenance script failed: " + result.second();
hostsSkipped.add(new HostSkipped(host, msg));
return new Ternary<>(true, true, result.second());
}
return new Ternary<>(true, false, result.second());
}
if (result.third() && !hostsToAvoidMaintenance.containsKey(host.getId())) {
s_logger.debug("Host " + host.getId() + " added to the avoid maintenance set");
hostsToAvoidMaintenance.put(host.getId(), "Pre-maintenance stage set to avoid maintenance");
}
return new Ternary<>(false, false, result.second());
}
/**
* Disable cluster (if hasn't been disabled yet)
* @param cluster cluster to disable
* @param disabledClusters set of disabled cluster ids. cluster is added if it is disabled
*/
private void disableClusterIfEnabled(Cluster cluster, Set<Long> disabledClusters) {
if (cluster.getAllocationState() == Grouping.AllocationState.Enabled && !disabledClusters.contains(cluster.getId())) {
updateCluster(cluster.getId(), "Disabled");
disabledClusters.add(cluster.getId());
}
}
private boolean isMaintenanceScriptDefinedOnHost(Host host, List<HostSkipped> hostsSkipped) {
try {
RollingMaintenanceAnswer answer = (RollingMaintenanceAnswer) agentManager.send(host.getId(), new RollingMaintenanceCommand(true));
return answer.isMaintenaceScriptDefined();
} catch (AgentUnavailableException | OperationTimedoutException e) {
String msg = "Could not check for maintenance script on host " + host.getId() + " due to: " + e.getMessage();
s_logger.error(msg, e);
return false;
}
}
/**
* Execute stage on host
* @return tuple: (SUCCESS, DETAILS, AVOID_MAINTENANCE) where:
* - SUCCESS: True if stage is successfull
* - DETAILS: Information retrieved by the host after executing the stage
* - AVOID_MAINTENANCE: True if maintenance stage must be avoided
*/
private Ternary<Boolean, String, Boolean> performStageOnHost(Host host, Stage stage, int timeout,
String payload, Boolean forced) throws InterruptedException {
Ternary<Boolean, String, Boolean> result = sendRollingMaintenanceCommandToHost(host, stage, timeout, payload);
if (!result.first() && !forced) {
throw new CloudRuntimeException("Stage: " + stage.toString() + " failed on host " + host.getUuid() + ": " + result.second());
}
return result;
}
/**
* Send rolling maintenance command to a host to perform a certain stage specified in cmd
* @return tuple: (SUCCESS, DETAILS, AVOID_MAINTENANCE) where:
* - SUCCESS: True if stage is successfull
* - DETAILS: Information retrieved by the host after executing the stage
* - AVOID_MAINTENANCE: True if maintenance stage must be avoided
*/
private Ternary<Boolean, String, Boolean> sendRollingMaintenanceCommandToHost(Host host, Stage stage,
int timeout, String payload) throws InterruptedException {
boolean completed = false;
Answer answer = null;
long timeSpent = 0L;
long pingInterval = KvmRollingMaintenancePingInterval.value() * 1000L;
boolean avoidMaintenance = false;
RollingMaintenanceCommand cmd = new RollingMaintenanceCommand(stage.toString());
cmd.setWait(timeout);
cmd.setPayload(payload);
while (!completed && timeSpent < timeout * 1000L) {
try {
answer = agentManager.send(host.getId(), cmd);
} catch (AgentUnavailableException | OperationTimedoutException e) {
// Agent may be restarted on the scripts - continue polling until it is up
String msg = "Cannot send command to host: " + host.getId() + ", waiting " + pingInterval + "ms - " + e.getMessage();
s_logger.warn(msg);
cmd.setStarted(true);
Thread.sleep(pingInterval);
timeSpent += pingInterval;
continue;
}
cmd.setStarted(true);
RollingMaintenanceAnswer rollingMaintenanceAnswer = (RollingMaintenanceAnswer) answer;
completed = rollingMaintenanceAnswer.isFinished();
if (!completed) {
Thread.sleep(pingInterval);
timeSpent += pingInterval;
} else {
avoidMaintenance = rollingMaintenanceAnswer.isAvoidMaintenance();
}
}
if (timeSpent >= timeout * 1000L) {
return new Ternary<>(false,
"Timeout exceeded for rolling maintenance on host " + host.getUuid() + " and stage " + stage.toString(),
avoidMaintenance);
}
return new Ternary<>(answer.getResult(), answer.getDetails(), avoidMaintenance);
}
/**
* Pre flight checks on hosts
*/
private void performPreFlightChecks(List<Host> hosts, int timeout, String payload, Boolean forced,
Map<Long, String> hostsToAvoidMaintenance) throws InterruptedException {
for (Host host : hosts) {
Ternary<Boolean, String, Boolean> result = performStageOnHost(host, Stage.PreFlight, timeout, payload, forced);
if (result.third() && !hostsToAvoidMaintenance.containsKey(host.getId())) {
s_logger.debug("Host " + host.getId() + " added to the avoid maintenance set");
hostsToAvoidMaintenance.put(host.getId(), "Pre-flight stage set to avoid maintenance");
}
}
}
/**
* Capacity checks on hosts
*/
private void performCapacityChecks(Cluster cluster, List<Host> hosts, Boolean forced) {
for (Host host : hosts) {
Pair<Boolean, String> result = performCapacityChecksBeforeHostInMaintenance(host, cluster);
if (!result.first() && !forced) {
throw new CloudRuntimeException("Capacity check failed for host " + host.getUuid() + ": " + result.second());
}
}
}
/**
* Check if there is enough capacity for host to enter maintenance
*/
private Pair<Boolean, String> performCapacityChecksBeforeHostInMaintenance(Host host, Cluster cluster) {
List<HostVO> hosts = hostDao.findByClusterId(cluster.getId());
List<Host> hostsInCluster = hosts.stream()
.filter(x -> x.getId() != host.getId() &&
x.getClusterId().equals(cluster.getId()) &&
x.getResourceState() == ResourceState.Enabled &&
x.getStatus() == Status.Up)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(hostsInCluster)) {
throw new CloudRuntimeException("No host available in cluster " + cluster.getUuid() + " (" + cluster.getName() + ") to support host " +
host.getUuid() + " (" + host.getName() + ") in maintenance");
}
List<VMInstanceVO> vmsRunning = vmInstanceDao.listByHostId(host.getId());
if (CollectionUtils.isEmpty(vmsRunning)) {
return new Pair<>(true, "OK");
}
List<String> hostTags = hostTagsDao.gethostTags(host.getId());
int sucessfullyCheckedVmMigrations = 0;
for (VMInstanceVO runningVM : vmsRunning) {
boolean canMigrateVm = false;
ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(runningVM.getServiceOfferingId());
for (Host hostInCluster : hostsInCluster) {
if (!checkHostTags(hostTags, hostTagsDao.gethostTags(hostInCluster.getId()), serviceOffering.getHostTag())) {
s_logger.debug("Host tags mismatch between host " + host.getUuid() + " and host " + hostInCluster.getUuid() +
". Skipping it from the capacity check");
continue;
}
DeployDestination deployDestination = new DeployDestination(null, null, null, host);
VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(runningVM);
boolean affinityChecks = true;
for (AffinityGroupProcessor affinityProcessor : _affinityProcessors) {
affinityChecks = affinityChecks && affinityProcessor.check(vmProfile, deployDestination);
}
if (!affinityChecks) {
s_logger.debug("Affinity check failed between host " + host.getUuid() + " and host " + hostInCluster.getUuid() +
". Skipping it from the capacity check");
continue;
}
boolean maxGuestLimit = capacityManager.checkIfHostReachMaxGuestLimit(host);
boolean hostHasCPUCapacity = capacityManager.checkIfHostHasCpuCapability(hostInCluster.getId(), serviceOffering.getCpu(), serviceOffering.getSpeed());
int cpuRequested = serviceOffering.getCpu() * serviceOffering.getSpeed();
long ramRequested = serviceOffering.getRamSize() * 1024L * 1024L;
ClusterDetailsVO clusterDetailsCpuOvercommit = clusterDetailsDao.findDetail(cluster.getId(), "cpuOvercommitRatio");
ClusterDetailsVO clusterDetailsRamOvercommmt = clusterDetailsDao.findDetail(cluster.getId(), "memoryOvercommitRatio");
Float cpuOvercommitRatio = Float.parseFloat(clusterDetailsCpuOvercommit.getValue());
Float memoryOvercommitRatio = Float.parseFloat(clusterDetailsRamOvercommmt.getValue());
boolean hostHasCapacity = capacityManager.checkIfHostHasCapacity(hostInCluster.getId(), cpuRequested, ramRequested, false,
cpuOvercommitRatio, memoryOvercommitRatio, false);
if (!maxGuestLimit && hostHasCPUCapacity && hostHasCapacity) {
canMigrateVm = true;
break;
}
}
if (!canMigrateVm) {
String msg = "VM " + runningVM.getUuid() + " cannot be migrated away from host " + host.getUuid() +
" to any other host in the cluster";
s_logger.error(msg);
return new Pair<>(false, msg);
}
sucessfullyCheckedVmMigrations++;
}
if (sucessfullyCheckedVmMigrations != vmsRunning.size()) {
return new Pair<>(false, "Host " + host.getId() + " cannot enter maintenance mode as capacity check failed for hosts in cluster " + cluster.getUuid());
}
return new Pair<>(true, "OK");
}
/**
* Check hosts tags
*/
private boolean checkHostTags(List<String> hostTags, List<String> hostInClusterTags, String offeringTag) {
if (CollectionUtils.isEmpty(hostTags) && CollectionUtils.isEmpty(hostInClusterTags)) {
return true;
} else if ((CollectionUtils.isNotEmpty(hostTags) && CollectionUtils.isEmpty(hostInClusterTags)) ||
(CollectionUtils.isEmpty(hostTags) && CollectionUtils.isNotEmpty(hostInClusterTags))) {
return false;
} else {
return hostInClusterTags.contains(offeringTag);
}
}
/**
* Retrieve all the hosts in 'Up' state within the scope for starting rolling maintenance
*/
protected Map<Long, List<Host>> getHostsByClusterForRollingMaintenance(ResourceType type, List<Long> ids) {
Set<Host> hosts = new HashSet<>();
List<HostVO> hostsInScope = null;
for (Long id : ids) {
if (type == ResourceType.Host) {
hostsInScope = Collections.singletonList(hostDao.findById(id));
} else if (type == ResourceType.Cluster) {
hostsInScope = hostDao.findByClusterId(id);
} else if (type == ResourceType.Pod) {
hostsInScope = hostDao.findByPodId(id);
} else if (type == ResourceType.Zone) {
hostsInScope = hostDao.findByDataCenterId(id);
}
List<HostVO> hostsUp = hostsInScope.stream()
.filter(x -> x.getHypervisorType() == Hypervisor.HypervisorType.KVM)
.collect(Collectors.toList());
hosts.addAll(hostsUp);
}
return hosts.stream().collect(Collectors.groupingBy(Host::getClusterId));
}
@Override
public Pair<ResourceType, List<Long>> getResourceTypeIdPair(StartRollingMaintenanceCmd cmd) {
return getResourceTypeAndIdPair(cmd.getPodIds(), cmd.getClusterIds(), cmd.getZoneIds(), cmd.getHostIds());
}
/*
Wait for to be in maintenance mode
*/
private void waitForHostInMaintenance(long hostId) throws CloudRuntimeException, InterruptedException {
HostVO host = hostDao.findById(hostId);
long timeout = KvmRollingMaintenanceWaitForMaintenanceTimeout.value() * 1000L;
long timeSpent = 0;
long step = 30 * 1000L;
while (timeSpent < timeout && host.getResourceState() != ResourceState.Maintenance) {
Thread.sleep(step);
timeSpent += step;
host = hostDao.findById(hostId);
}
if (host.getResourceState() != ResourceState.Maintenance) {
String errorMsg = "Timeout: waited " + timeout + "ms for host " + host.getUuid() + "(" + host.getName() + ")" +
" to be in Maintenance state, but after timeout it is in " + host.getResourceState().toString() + " state";
s_logger.error(errorMsg);
throw new CloudRuntimeException(errorMsg);
}
s_logger.debug("Host " + host.getUuid() + "(" + host.getName() + ") is in maintenance");
}
@Override
public String getConfigComponentName() {
return RollingMaintenanceManagerImpl.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {KvmRollingMaintenanceStageTimeout, KvmRollingMaintenancePingInterval, KvmRollingMaintenanceWaitForMaintenanceTimeout};
}
}

View File

@ -166,6 +166,7 @@ import org.apache.cloudstack.api.command.admin.resource.CleanVMReservationsCmd;
import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd;
import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd; import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd;
import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd; import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd;
import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd; import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd;
import org.apache.cloudstack.api.command.admin.router.ConfigureVirtualRouterElementCmd; import org.apache.cloudstack.api.command.admin.router.ConfigureVirtualRouterElementCmd;
@ -3128,6 +3129,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(GetUploadParamsForIsoCmd.class); cmdList.add(GetUploadParamsForIsoCmd.class);
cmdList.add(ListTemplateOVFProperties.class); cmdList.add(ListTemplateOVFProperties.class);
cmdList.add(GetRouterHealthCheckResultsCmd.class); cmdList.add(GetRouterHealthCheckResultsCmd.class);
cmdList.add(StartRollingMaintenanceCmd.class);
// Out-of-band management APIs for admins // Out-of-band management APIs for admins
cmdList.add(EnableOutOfBandManagementForHostCmd.class); cmdList.add(EnableOutOfBandManagementForHostCmd.class);

View File

@ -313,4 +313,8 @@
<constructor-arg name="timeout" value="10000" /> <constructor-arg name="timeout" value="10000" />
</bean> </bean>
<bean id="rollingMaintenanceManager" class="com.cloud.resource.RollingMaintenanceManagerImpl">
<property name="affinityGroupProcessors"
value="#{affinityProcessorsRegistry.registered}" />
</bean>
</beans> </beans>

View File

@ -621,6 +621,11 @@ public class MockResourceManagerImpl extends ManagerBase implements ResourceMana
return null; return null;
} }
@Override
public boolean cancelMaintenance(long hostId) {
return false;
}
@Override @Override
public boolean isHostGpuEnabled(final long hostId) { public boolean isHostGpuEnabled(final long hostId) {
// TODO Auto-generated method stub // TODO Auto-generated method stub

View File

@ -0,0 +1,167 @@
// 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.resource;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.org.Cluster;
import com.cloud.utils.exception.CloudRuntimeException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class RollingMaintenanceManagerImplTest {
@Mock
HostDao hostDao;
@Mock
HostVO host1;
@Mock
HostVO host2;
@Mock
HostVO host3;
@Mock
HostVO host4;
@Mock
Cluster cluster;
@Spy
@InjectMocks
private RollingMaintenanceManagerImpl manager = new RollingMaintenanceManagerImpl();
// Hosts in cluster 1
private static final long hostId1 = 1L;
private static final long hostId2 = 2L;
// Hosts in cluster 2
private static final long hostId3 = 3L;
private static final long hostId4 = 4L;
private static final long clusterId1 = 1L;
private static final long clusterId2 = 2L;
private static final long podId = 1L;
private static final long zoneId = 1L;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
Mockito.when(hostDao.findByClusterId(clusterId1)).thenReturn(Arrays.asList(host1, host2));
Mockito.when(hostDao.findByClusterId(clusterId2)).thenReturn(Arrays.asList(host3, host4));
List<HostVO> hosts = Arrays.asList(host1, host2, host3, host4);
Mockito.when(hostDao.findByPodId(podId)).thenReturn(hosts);
Mockito.when(hostDao.findByDataCenterId(zoneId)).thenReturn(hosts);
for (HostVO host : hosts) {
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
Mockito.when(host.getState()).thenReturn(Status.Up);
Mockito.when(host.isInMaintenanceStates()).thenReturn(false);
}
Mockito.when(host1.getClusterId()).thenReturn(clusterId1);
Mockito.when(host2.getClusterId()).thenReturn(clusterId1);
Mockito.when(host3.getClusterId()).thenReturn(clusterId2);
Mockito.when(host4.getClusterId()).thenReturn(clusterId2);
Mockito.when(hostDao.findById(hostId1)).thenReturn(host1);
Mockito.when(hostDao.findById(hostId2)).thenReturn(host2);
Mockito.when(hostDao.findById(hostId3)).thenReturn(host3);
Mockito.when(hostDao.findById(hostId4)).thenReturn(host4);
Mockito.when(host1.getStatus()).thenReturn(Status.Up);
Mockito.when(host2.getStatus()).thenReturn(Status.Up);
Mockito.when(host1.getResourceState()).thenReturn(ResourceState.Enabled);
Mockito.when(host2.getResourceState()).thenReturn(ResourceState.Enabled);
}
private void checkResults(Map<Long, List<Host>> result) {
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.containsKey(clusterId1));
Assert.assertTrue(result.containsKey(clusterId2));
List<Host> cluster1Hosts = result.get(clusterId1);
List<Host> cluster2Hosts = result.get(clusterId2);
Assert.assertEquals(2, cluster1Hosts.size());
Assert.assertTrue(cluster1Hosts.contains(host1));
Assert.assertTrue(cluster1Hosts.contains(host2));
Assert.assertEquals(2, cluster2Hosts.size());
Assert.assertTrue(cluster2Hosts.contains(host3));
Assert.assertTrue(cluster2Hosts.contains(host4));
}
@Test
public void testGetHostsByClusterForRollingMaintenanceZoneScope() {
Map<Long, List<Host>> result = manager.getHostsByClusterForRollingMaintenance(RollingMaintenanceManager.ResourceType.Zone, Collections.singletonList(zoneId));
checkResults(result);
}
@Test
public void testGetHostsByClusterForRollingMaintenancePodScope() {
Map<Long, List<Host>> result = manager.getHostsByClusterForRollingMaintenance(RollingMaintenanceManager.ResourceType.Pod, Collections.singletonList(podId));
checkResults(result);
}
@Test
public void testGetHostsByClusterForRollingMaintenanceClusterScope() {
List<Long> clusterIds = Arrays.asList(clusterId1, clusterId2);
Map<Long, List<Host>> result = manager.getHostsByClusterForRollingMaintenance(RollingMaintenanceManager.ResourceType.Cluster, clusterIds);
checkResults(result);
}
@Test
public void testGetHostsByClusterForRollingMaintenanceHostScope() {
List<Long> hostIds = Arrays.asList(hostId1, hostId2, hostId3, hostId4);
Map<Long, List<Host>> result = manager.getHostsByClusterForRollingMaintenance(RollingMaintenanceManager.ResourceType.Host, hostIds);
checkResults(result);
}
@Test(expected = CloudRuntimeException.class)
public void testPerformStateChecksNotForce() {
List<Host> hosts = Arrays.asList(host1, host2);
Mockito.when(host1.getStatus()).thenReturn(Status.Error);
manager.performStateChecks(cluster, hosts, false, new ArrayList<>());
}
@Test
public void testPerformStateChecksForce() {
List<Host> hosts = new ArrayList<>();
hosts.add(host1);
hosts.add(host2);
Mockito.when(host1.getStatus()).thenReturn(Status.Error);
List<RollingMaintenanceManager.HostSkipped> skipped = new ArrayList<>();
manager.performStateChecks(cluster, hosts, true, skipped);
Assert.assertFalse(skipped.isEmpty());
Assert.assertEquals(1, skipped.size());
Assert.assertEquals(host1, skipped.get(0).getHost());
Assert.assertEquals(1, hosts.size());
}
}

View File

@ -193,7 +193,9 @@ known_categories = {
'Restore' : 'Backup and Recovery', 'Restore' : 'Backup and Recovery',
'UnmanagedInstance': 'Virtual Machine', 'UnmanagedInstance': 'Virtual Machine',
'KubernetesSupportedVersion': 'Kubernetes Service', 'KubernetesSupportedVersion': 'Kubernetes Service',
'KubernetesCluster': 'Kubernetes Service' 'KubernetesCluster': 'Kubernetes Service',
'UnmanagedInstance': 'Virtual Machine',
'Rolling': 'Rolling Maintenance'
} }

View File

@ -12669,6 +12669,14 @@ div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-it
background-position: -100px -614px; background-position: -100px -614px;
} }
.startRollingMaintenance .icon {
background-position: -138px -65px;
}
.startRollingMaintenance:hover .icon {
background-position: -138px -65px;
}
.addVlanRange .icon, .addVlanRange .icon,
.addVmwareDc .icon { .addVmwareDc .icon {
background-position: -37px -62px; background-position: -37px -62px;

View File

@ -1669,6 +1669,9 @@ var dictionary = {
"label.start.lb.vm":"Start LB VM", "label.start.lb.vm":"Start LB VM",
"label.start.port":"Start Port", "label.start.port":"Start Port",
"label.start.reserved.system.IP":"Start Reserved system IP", "label.start.reserved.system.IP":"Start Reserved system IP",
"label.start.rolling.maintenance":"Start Rolling Maintenance",
"label.start.rolling.maintenance.force":"Force",
"label.start.rolling.maintenance.payload":"Payload",
"label.start.vlan":"Start VLAN", "label.start.vlan":"Start VLAN",
"label.start.vxlan":"Start VXLAN", "label.start.vxlan":"Start VXLAN",
"label.state":"State", "label.state":"State",

View File

@ -258,6 +258,100 @@
return allowedActions; return allowedActions;
}; };
var rollingMaintenanceAction = function(args) {
var isCluster = args.entity === 'clusters';
var isZone = args.entity === 'zones';
var isPod = args.entity === 'pods';
var isHost = args.entity === 'hosts';
var action = {
messages: {
notification: function(args) {
return 'label.start.rolling.maintenance';
}
},
label: 'label.start.rolling.maintenance',
addRow: 'false',
createForm: {
title: 'label.start.rolling.maintenance',
fields: {
timeout: {
label: 'label.timeout',
},
force: {
isBoolean: true,
label: 'label.start.rolling.maintenance.force'
},
payload: {
label: 'label.start.rolling.maintenance.payload'
}
}
},
action: function(args) {
var selectedIds;
if (isCluster) {
selectedIds = args.context.clusters.map(x => x.id);
} else if (isZone) {
selectedIds = args.context.physicalResources.map(x => x.id);
} else if (isPod) {
selectedIds = args.context.pods.map(x => x.id);
} else if (isHost) {
selectedIds = args.context.hosts.map(x => x.id);
}
var ids = selectedIds.join(',');
var data = {
force: args.data.force,
timeout: args.data.timeout,
payload: args.data.payload
};
if (isCluster) {
$.extend(data, {
clusterids : ids
});
} else if (isZone) {
$.extend(data, {
zoneids : ids
});
} else if (isPod) {
$.extend(data, {
podids : ids
});
} else if (isHost) {
$.extend(data, {
hostids : ids
});
}
$.ajax({
url: createURL("startRollingMaintenance"),
dataType: "json",
data: data,
async: true,
success: function (json) {
var item = json.startrollingmaintenanceresponse;
var jid = item.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
};
if (args && args.listView) {
$.extend(action, {
isHeader: true,
isMultiSelectAction: true
});
}
return action;
};
cloudStack.sections.system = { cloudStack.sections.system = {
title: 'label.menu.infrastructure', title: 'label.menu.infrastructure',
id: 'system', id: 'system',
@ -7666,6 +7760,7 @@
zones: { zones: {
id: 'physicalResources', id: 'physicalResources',
label: 'label.menu.physical.resources', label: 'label.menu.physical.resources',
multiSelect: true,
fields: { fields: {
name: { name: {
label: 'label.zone' label: 'label.zone'
@ -7755,12 +7850,65 @@
return 'label.metrics'; return 'label.metrics';
} }
} }
} },
startRollingMaintenance: rollingMaintenanceAction({ listView: true, entity: 'zones' })
}, },
detailView: { detailView: {
isMaximized: true, isMaximized: true,
actions: { actions: {
startRollingMaintenance: {
label: 'label.start.rolling.maintenance',
textLabel: 'label.start.rolling.maintenance',
messages: {
notification: function (args) {
return 'label.start.rolling.maintenance';
}
},
createForm: {
title: 'label.start.rolling.maintenance',
fields: {
timeout: {
label: 'label.timeout',
},
force: {
isBoolean: true,
label: 'label.start.rolling.maintenance.force'
},
payload: {
label: 'label.start.rolling.maintenance.payload'
}
}
},
action: function (args) {
var data = {
zoneids: args.context.physicalResources[0].id,
force: args.data.force,
timeout: args.data.timeout,
payload: args.data.payload
};
$.ajax({
url: createURL("startRollingMaintenance"),
dataType: "json",
data: data,
async: true,
success: function (json) {
var item = json.rollingmaintenance;
args.response.success({
actionFilter: zoneActionfilter,
data: item
});
}
});
},
notification: {
poll: function (args) {
args.complete();
}
}
},
addVmwareDc: { addVmwareDc: {
label: 'label.add.vmware.datacenter', label: 'label.add.vmware.datacenter',
textLabel: 'label.add.vmware.datacenter', textLabel: 'label.add.vmware.datacenter',
@ -13792,6 +13940,7 @@
listView: { listView: {
id: 'pods', id: 'pods',
section: 'pods', section: 'pods',
multiSelect: true,
fields: { fields: {
name: { name: {
label: 'label.name' label: 'label.name'
@ -14053,7 +14202,8 @@
return 'label.add.pod'; return 'label.add.pod';
} }
} }
} },
startRollingMaintenance: rollingMaintenanceAction({ listView: true, entity: 'pods' })
}, },
detailView: { detailView: {
@ -14075,6 +14225,57 @@
return hiddenTabs; return hiddenTabs;
}, },
actions: { actions: {
startRollingMaintenance: {
label: 'label.start.rolling.maintenance',
textLabel: 'label.start.rolling.maintenance',
messages: {
notification: function (args) {
return 'label.start.rolling.maintenance';
}
},
createForm: {
title: 'label.start.rolling.maintenance',
fields: {
timeout: {
label: 'label.timeout',
},
force: {
isBoolean: true,
label: 'label.start.rolling.maintenance.force'
},
payload: {
label: 'label.start.rolling.maintenance.payload'
}
}
},
action: function (args) {
var data = {
podids: args.context.pods[0].id,
force: args.data.force,
timeout: args.data.timeout,
payload: args.data.payload
};
$.ajax({
url: createURL("startRollingMaintenance"),
dataType: "json",
data: data,
async: true,
success: function (json) {
var item = json.rollingmaintenance;
args.response.success({
actionFilter: zoneActionfilter,
data: item
});
}
});
},
notification: {
poll: function (args) {
args.complete();
}
}
},
edit: { edit: {
label: 'label.edit', label: 'label.edit',
action: function (args) { action: function (args) {
@ -14446,6 +14647,7 @@
listView: { listView: {
id: 'clusters', id: 'clusters',
section: 'clusters', section: 'clusters',
multiSelect: true,
fields: { fields: {
name: { name: {
label: 'label.name' label: 'label.name'
@ -15184,7 +15386,8 @@
return 'label.metrics'; return 'label.metrics';
} }
} }
} },
startRollingMaintenance: rollingMaintenanceAction({ listView: true, entity: 'clusters' })
}, },
detailView: { detailView: {
@ -15215,6 +15418,56 @@
actions: { actions: {
startRollingMaintenance: {
label: 'label.start.rolling.maintenance',
textLabel: 'label.start.rolling.maintenance',
messages: {
notification: function (args) {
return 'label.start.rolling.maintenance';
}
},
createForm: {
title: 'label.start.rolling.maintenance',
fields: {
timeout: {
label: 'label.timeout',
},
force: {
isBoolean: true,
label: 'label.start.rolling.maintenance.force'
},
payload: {
label: 'label.start.rolling.maintenance.payload'
}
}
},
action: function (args) {
var data = {
clusterids: args.context.clusters[0].id,
force: args.data.force,
timeout: args.data.timeout,
payload: args.data.payload
};
$.ajax({
url: createURL("startRollingMaintenance"),
dataType: "json",
data: data,
async: true,
success: function (json) {
var item = json.rollingmaintenance;
args.response.success({
actionFilter: zoneActionfilter,
data: item
});
}
});
},
notification: {
poll: function (args) {
args.complete();
}
}
},
edit: { edit: {
label: 'label.edit', label: 'label.edit',
action: function (args) { action: function (args) {
@ -16002,6 +16255,7 @@
listView: { listView: {
section: 'hosts', section: 'hosts',
id: 'hosts', id: 'hosts',
multiSelect: true,
fields: { fields: {
name: { name: {
label: 'label.name' label: 'label.name'
@ -16697,7 +16951,8 @@
return 'label.metrics'; return 'label.metrics';
} }
} }
} },
startRollingMaintenance: rollingMaintenanceAction({ listView: true, entity: 'hosts' })
}, },
detailView: { detailView: {
name: "Host details", name: "Host details",
@ -16706,6 +16961,56 @@
path: 'instances' path: 'instances'
}, },
actions: { actions: {
startRollingMaintenance: {
label: 'label.start.rolling.maintenance',
textLabel: 'label.start.rolling.maintenance',
messages: {
notification: function (args) {
return 'label.start.rolling.maintenance';
}
},
createForm: {
title: 'label.start.rolling.maintenance',
fields: {
timeout: {
label: 'label.timeout',
},
force: {
isBoolean: true,
label: 'label.start.rolling.maintenance.force'
},
payload: {
label: 'label.start.rolling.maintenance.payload'
}
}
},
action: function (args) {
var data = {
hostids: args.context.hosts[0].id,
force: args.data.force,
timeout: args.data.timeout,
payload: args.data.payload
};
$.ajax({
url: createURL("startRollingMaintenance"),
dataType: "json",
data: data,
async: true,
success: function (json) {
var item = json.rollingmaintenance;
args.response.success({
actionFilter: zoneActionfilter,
data: item
});
}
});
},
notification: {
poll: function (args) {
args.complete();
}
}
},
edit: { edit: {
label: 'label.edit', label: 'label.edit',
action: function (args) { action: function (args) {
@ -22173,6 +22478,7 @@
allowedActions.push("disableHA"); allowedActions.push("disableHA");
} }
allowedActions.push("startRollingMaintenance");
return allowedActions; return allowedActions;
} }
@ -22224,6 +22530,7 @@
//$("#tab_ipallocation, #add_iprange_button, #tab_network_device, #add_network_device_button").hide(); //$("#tab_ipallocation, #add_iprange_button, #tab_network_device, #add_network_device_button").hide();
} }
allowedActions.push("startRollingMaintenance");
return allowedActions; return allowedActions;
} }
@ -22270,6 +22577,7 @@
allowedActions.push("disableHA"); allowedActions.push("disableHA");
} }
allowedActions.push("startRollingMaintenance");
return allowedActions; return allowedActions;
} }
@ -22292,12 +22600,16 @@
if (jsonObj.hypervisor == "KVM") { if (jsonObj.hypervisor == "KVM") {
allowedActions.push("secureKVMHost"); allowedActions.push("secureKVMHost");
allowedActions.push("startRollingMaintenance");
} }
} else if (jsonObj.resourcestate == "ErrorInMaintenance") { } else if (jsonObj.resourcestate == "ErrorInMaintenance") {
allowedActions.push("edit"); allowedActions.push("edit");
allowedActions.push("enableMaintenanceMode"); allowedActions.push("enableMaintenanceMode");
allowedActions.push("cancelMaintenanceMode"); allowedActions.push("cancelMaintenanceMode");
if (jsonObj.hypervisor == "KVM") {
allowedActions.push("startRollingMaintenance");
}
} else if (jsonObj.resourcestate == "PrepareForMaintenance" || jsonObj.resourcestate == 'ErrorInPrepareForMaintenance') { } else if (jsonObj.resourcestate == "PrepareForMaintenance" || jsonObj.resourcestate == 'ErrorInPrepareForMaintenance') {
allowedActions.push("edit"); allowedActions.push("edit");
allowedActions.push("cancelMaintenanceMode"); allowedActions.push("cancelMaintenanceMode");

View File

@ -66,6 +66,10 @@ public class Script implements Callable<String> {
Process _process; Process _process;
Thread _thread; Thread _thread;
public boolean isTimeout() {
return _isTimeOut;
}
public int getExitValue() { public int getExitValue() {
return _process.exitValue(); return _process.exitValue();
} }