Merge remote-tracking branch 'apache/4.18' into main

This commit is contained in:
Daan Hoogland 2023-11-03 11:55:26 +01:00
commit a15cb81c85
7 changed files with 251 additions and 77 deletions

View File

@ -42,6 +42,7 @@ import javax.naming.ConfigurationException;
import com.cloud.resource.AgentStatusUpdater; import com.cloud.resource.AgentStatusUpdater;
import com.cloud.resource.ResourceStatusUpdater; import com.cloud.resource.ResourceStatusUpdater;
import com.cloud.agent.api.PingAnswer;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import org.apache.cloudstack.agent.lb.SetupMSListAnswer; import org.apache.cloudstack.agent.lb.SetupMSListAnswer;
import org.apache.cloudstack.agent.lb.SetupMSListCommand; import org.apache.cloudstack.agent.lb.SetupMSListCommand;
@ -842,6 +843,9 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater
listener.processControlResponse(response, (AgentControlAnswer)answer); listener.processControlResponse(response, (AgentControlAnswer)answer);
} }
} }
} else if (answer instanceof PingAnswer && (((PingAnswer) answer).isSendStartup()) && _reconnectAllowed) {
s_logger.info("Management server requested startup command to reinitialize the agent");
sendStartup(link);
} else { } else {
setLastPingResponseTime(); setLastPingResponseTime();
} }

View File

@ -22,15 +22,26 @@ package com.cloud.agent.api;
public class PingAnswer extends Answer { public class PingAnswer extends Answer {
private PingCommand _command = null; private PingCommand _command = null;
private boolean sendStartup = false;
protected PingAnswer() { protected PingAnswer() {
} }
public PingAnswer(PingCommand cmd) { public PingAnswer(PingCommand cmd, boolean sendStartup) {
super(cmd); super(cmd);
_command = cmd; _command = cmd;
this.sendStartup = sendStartup;
} }
public PingCommand getCommand() { public PingCommand getCommand() {
return _command; return _command;
} }
public boolean isSendStartup() {
return sendStartup;
}
public void setSendStartup(boolean sendStartup) {
this.sendStartup = sendStartup;
}
} }

View File

@ -40,6 +40,7 @@ import javax.naming.ConfigurationException;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.GlobalLock;
import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -799,49 +800,65 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
return true; return true;
} }
protected Status getNextStatusOnDisconnection(Host host, final Status.Event event) {
final Status currentStatus = host.getStatus();
Status nextStatus;
if (currentStatus == Status.Down || currentStatus == Status.Alert || currentStatus == Status.Removed) {
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("Host %s is already %s", host.getUuid(), currentStatus));
}
nextStatus = currentStatus;
} else {
try {
nextStatus = currentStatus.getNextStatus(event);
} catch (final NoTransitionException e) {
final String err = String.format("Cannot find next status for %s as current status is %s for agent %s", event, currentStatus, host.getUuid());
s_logger.debug(err);
throw new CloudRuntimeException(err);
}
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("The next status of agent %s is %s, current status is %s", host.getUuid(), nextStatus, currentStatus));
}
}
return nextStatus;
}
protected boolean handleDisconnectWithoutInvestigation(final AgentAttache attache, final Status.Event event, final boolean transitState, final boolean removeAgent) { protected boolean handleDisconnectWithoutInvestigation(final AgentAttache attache, final Status.Event event, final boolean transitState, final boolean removeAgent) {
final long hostId = attache.getId(); final long hostId = attache.getId();
s_logger.info("Host " + hostId + " is disconnecting with event " + event); boolean result = false;
Status nextStatus = null; GlobalLock joinLock = getHostJoinLock(hostId);
final HostVO host = _hostDao.findById(hostId); if (joinLock.lock(60)) {
if (host == null) { try {
s_logger.warn("Can't find host with " + hostId); s_logger.info(String.format("Host %d is disconnecting with event %s", hostId, event));
nextStatus = Status.Removed; Status nextStatus = null;
} else { final HostVO host = _hostDao.findById(hostId);
final Status currentStatus = host.getStatus(); if (host == null) {
if (currentStatus == Status.Down || currentStatus == Status.Alert || currentStatus == Status.Removed) { s_logger.warn(String.format("Can't find host with %d", hostId));
if (s_logger.isDebugEnabled()) { nextStatus = Status.Removed;
s_logger.debug("Host " + hostId + " is already " + currentStatus); } else {
} nextStatus = getNextStatusOnDisconnection(host, event);
nextStatus = currentStatus; caService.purgeHostCertificate(host);
} else {
try {
nextStatus = currentStatus.getNextStatus(event);
} catch (final NoTransitionException e) {
final String err = "Cannot find next status for " + event + " as current status is " + currentStatus + " for agent " + hostId;
s_logger.debug(err);
throw new CloudRuntimeException(err);
} }
if (s_logger.isDebugEnabled()) { if (s_logger.isDebugEnabled()) {
s_logger.debug("The next status of agent " + hostId + "is " + nextStatus + ", current status is " + currentStatus); s_logger.debug(String.format("Deregistering link for %d with state %s", hostId, nextStatus));
} }
removeAgent(attache, nextStatus);
if (host != null && transitState) {
// update the state for host in DB as per the event
disconnectAgent(host, event, _nodeId);
}
} finally {
joinLock.unlock();
} }
caService.purgeHostCertificate(host); result = true;
} }
joinLock.releaseRef();
if (s_logger.isDebugEnabled()) { return result;
s_logger.debug("Deregistering link for " + hostId + " with state " + nextStatus);
}
removeAgent(attache, nextStatus);
// update the DB
if (host != null && transitState) {
disconnectAgent(host, event, _nodeId);
}
return true;
} }
protected boolean handleDisconnectWithInvestigation(final AgentAttache attache, Status.Event event) { protected boolean handleDisconnectWithInvestigation(final AgentAttache attache, Status.Event event) {
@ -1102,26 +1119,23 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
return attache; return attache;
} }
private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[] startup, final Request request) { private AgentAttache sendReadyAndGetAttache(HostVO host, ReadyCommand ready, Link link, StartupCommand[] startup) throws ConnectionException {
AgentAttache attache = null; final List<String> agentMSHostList = new ArrayList<>();
ReadyCommand ready = null; String lbAlgorithm = null;
try { if (startup != null && startup.length > 0) {
final List<String> agentMSHostList = new ArrayList<>(); final String agentMSHosts = startup[0].getMsHostList();
String lbAlgorithm = null; if (StringUtils.isNotEmpty(agentMSHosts)) {
if (startup != null && startup.length > 0) { String[] msHosts = agentMSHosts.split("@");
final String agentMSHosts = startup[0].getMsHostList(); if (msHosts.length > 1) {
if (StringUtils.isNotEmpty(agentMSHosts)) { lbAlgorithm = msHosts[1];
String[] msHosts = agentMSHosts.split("@");
if (msHosts.length > 1) {
lbAlgorithm = msHosts[1];
}
agentMSHostList.addAll(Arrays.asList(msHosts[0].split(",")));
} }
agentMSHostList.addAll(Arrays.asList(msHosts[0].split(",")));
} }
}
final HostVO host = _resourceMgr.createHostVOForConnectedAgent(startup); AgentAttache attache = null;
if (host != null) { GlobalLock joinLock = getHostJoinLock(host.getId());
ready = new ReadyCommand(host.getDataCenterId(), host.getId(), NumbersUtil.enableHumanReadableSizes); if (joinLock.lock(60)) {
try {
if (!indirectAgentLB.compareManagementServerList(host.getId(), host.getDataCenterId(), agentMSHostList, lbAlgorithm)) { if (!indirectAgentLB.compareManagementServerList(host.getId(), host.getDataCenterId(), agentMSHostList, lbAlgorithm)) {
final List<String> newMSList = indirectAgentLB.getManagementServerList(host.getId(), host.getDataCenterId(), null); final List<String> newMSList = indirectAgentLB.getManagementServerList(host.getId(), host.getDataCenterId(), null);
@ -1133,6 +1147,24 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
attache = createAttacheForConnect(host, link); attache = createAttacheForConnect(host, link);
attache = notifyMonitorsOfConnection(attache, startup, false); attache = notifyMonitorsOfConnection(attache, startup, false);
} finally {
joinLock.unlock();
}
} else {
throw new ConnectionException(true, "Unable to acquire lock on host " + host.getUuid());
}
joinLock.releaseRef();
return attache;
}
private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[] startup, final Request request) {
AgentAttache attache = null;
ReadyCommand ready = null;
try {
final HostVO host = _resourceMgr.createHostVOForConnectedAgent(startup);
if (host != null) {
ready = new ReadyCommand(host.getDataCenterId(), host.getId(), NumbersUtil.enableHumanReadableSizes);
attache = sendReadyAndGetAttache(host, ready, link, startup);
} }
} catch (final Exception e) { } catch (final Exception e) {
s_logger.debug("Failed to handle host connection: ", e); s_logger.debug("Failed to handle host connection: ", e);
@ -1312,6 +1344,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
connectAgent(link, cmds, request); connectAgent(link, cmds, request);
} }
return; return;
} else if (cmd instanceof StartupCommand) {
connectAgent(link, cmds, request);
} }
final long hostId = attache.getId(); final long hostId = attache.getId();
@ -1366,7 +1400,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
handleCommands(attache, request.getSequence(), new Command[] {cmd}); handleCommands(attache, request.getSequence(), new Command[] {cmd});
if (cmd instanceof PingCommand) { if (cmd instanceof PingCommand) {
final long cmdHostId = ((PingCommand)cmd).getHostId(); final long cmdHostId = ((PingCommand)cmd).getHostId();
boolean requestStartupCommand = false;
final HostVO host = _hostDao.findById(Long.valueOf(cmdHostId));
boolean gatewayAccessible = true;
// if the router is sending a ping, verify the // if the router is sending a ping, verify the
// gateway was pingable // gateway was pingable
if (cmd instanceof PingRoutingCommand) { if (cmd instanceof PingRoutingCommand) {
@ -1391,7 +1428,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
s_logger.debug("Not processing " + PingRoutingCommand.class.getSimpleName() + " for agent id=" + cmdHostId + "; can't find the host in the DB"); s_logger.debug("Not processing " + PingRoutingCommand.class.getSimpleName() + " for agent id=" + cmdHostId + "; can't find the host in the DB");
} }
} }
answer = new PingAnswer((PingCommand)cmd); if (host!= null && host.getStatus() != Status.Up && gatewayAccessible) {
requestStartupCommand = true;
}
answer = new PingAnswer((PingCommand)cmd, requestStartupCommand);
} else if (cmd instanceof ReadyAnswer) { } else if (cmd instanceof ReadyAnswer) {
final HostVO host = _hostDao.findById(attache.getId()); final HostVO host = _hostDao.findById(attache.getId());
if (host == null) { if (host == null) {
@ -1913,4 +1953,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
sendCommandToAgents(hostsPerZone, params); sendCommandToAgents(hostsPerZone, params);
} }
} }
private GlobalLock getHostJoinLock(Long hostId) {
return GlobalLock.getInternLock(String.format("%s-%s", "Host-Join", hostId));
}
} }

View File

@ -1343,8 +1343,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
} }
s_logger.debug(String.format("The memory balloon stats period [%s] has been set successfully for the VM (Libvirt Domain) with ID [%s] and name [%s].", s_logger.debug(String.format("The memory balloon stats period [%s] has been set successfully for the VM (Libvirt Domain) with ID [%s] and name [%s].",
currentVmBalloonStatsPeriod, vmId, dm.getName())); currentVmBalloonStatsPeriod, vmId, dm.getName()));
} catch (final LibvirtException e) { } catch (final Exception e) {
s_logger.warn("Failed to set up memory balloon stats period." + e.getMessage()); s_logger.warn(String.format("Failed to set up memory balloon stats period for the VM %s with exception %s", parser.getName(), e.getMessage()));
} }
} }
} }

View File

@ -59,6 +59,8 @@ public class LibvirtDomainXMLParser {
private Integer vncPort; private Integer vncPort;
private String desc; private String desc;
private String name;
public boolean parseDomainXML(String domXML) { public boolean parseDomainXML(String domXML) {
DocumentBuilder builder; DocumentBuilder builder;
try { try {
@ -71,6 +73,7 @@ public class LibvirtDomainXMLParser {
Element rootElement = doc.getDocumentElement(); Element rootElement = doc.getDocumentElement();
desc = getTagValue("description", rootElement); desc = getTagValue("description", rootElement);
name = getTagValue("name", rootElement);
Element devices = (Element)rootElement.getElementsByTagName("devices").item(0); Element devices = (Element)rootElement.getElementsByTagName("devices").item(0);
NodeList disks = devices.getElementsByTagName("disk"); NodeList disks = devices.getElementsByTagName("disk");
@ -312,15 +315,19 @@ public class LibvirtDomainXMLParser {
String path = getTagValue("backend", rng); String path = getTagValue("backend", rng);
String bytes = getAttrValue("rate", "bytes", rng); String bytes = getAttrValue("rate", "bytes", rng);
String period = getAttrValue("rate", "period", rng); String period = getAttrValue("rate", "period", rng);
if (StringUtils.isAnyEmpty(bytes, period)) {
if (StringUtils.isEmpty(backendModel)) { s_logger.debug(String.format("Bytes and period in the rng section should not be null, please check the VM %s", name));
def = new RngDef(path, Integer.parseInt(bytes), Integer.parseInt(period));
} else { } else {
def = new RngDef(path, RngBackendModel.valueOf(backendModel.toUpperCase()), if (StringUtils.isEmpty(backendModel)) {
Integer.parseInt(bytes), Integer.parseInt(period)); def = new RngDef(path, Integer.parseInt(bytes), Integer.parseInt(period));
} else {
def = new RngDef(path, RngBackendModel.valueOf(backendModel.toUpperCase()),
Integer.parseInt(bytes), Integer.parseInt(period));
}
}
if (def != null) {
rngDefs.add(def);
} }
rngDefs.add(def);
} }
NodeList watchDogs = devices.getElementsByTagName("watchdog"); NodeList watchDogs = devices.getElementsByTagName("watchdog");
@ -427,4 +434,8 @@ public class LibvirtDomainXMLParser {
public String getDescription() { public String getDescription() {
return desc; return desc;
} }
public String getName() {
return name;
}
} }

View File

@ -0,0 +1,102 @@
# 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.
""" Check state transition of host from Alert to Up on Ping
"""
# Import Local Modules
from marvin.cloudstackTestCase import *
from marvin.lib.common import *
from marvin.lib.utils import *
from nose.plugins.attrib import attr
_multiprocess_shared_ = False
class TestHostPing(cloudstackTestCase):
def setUp(self, handler=logging.StreamHandler()):
self.logger = logging.getLogger('TestHM')
self.stream_handler = handler
self.logger.setLevel(logging.DEBUG)
self.logger.addHandler(self.stream_handler)
self.apiclient = self.testClient.getApiClient()
self.hypervisor = self.testClient.getHypervisorInfo()
self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__
self.dbConnection = self.testClient.getDbConnection()
self.services = self.testClient.getParsedTestDataConfig()
self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())
self.pod = get_pod(self.apiclient, self.zone.id)
self.cleanup = []
def tearDown(self):
super(TestHostPing, self).tearDown()
def checkHostStateInCloudstack(self, state, host_id):
try:
listHost = Host.list(
self.apiclient,
type='Routing',
zoneid=self.zone.id,
podid=self.pod.id,
id=host_id
)
self.assertEqual(
isinstance(listHost, list),
True,
"Check if listHost returns a valid response"
)
self.assertEqual(
len(listHost),
1,
"Check if listHost returns a host"
)
self.logger.debug(" Host state is %s " % listHost[0].state)
if listHost[0].state == state:
return True, 1
else:
return False, 1
except Exception as e:
self.logger.debug("Got exception %s" % e)
return False, 1
@attr(
tags=[
"advanced",
"advancedns",
"smoke",
"basic"],
required_hardware="true")
def test_01_host_ping_on_alert(self):
listHost = Host.list(
self.apiclient,
type='Routing',
zoneid=self.zone.id,
podid=self.pod.id,
)
for host in listHost:
self.logger.debug('Hypervisor = {}'.format(host.id))
hostToTest = listHost[0]
sql_query = "UPDATE host SET status = 'Alert' WHERE uuid = '" + hostToTest.id + "'"
self.dbConnection.execute(sql_query)
hostUpInCloudstack = wait_until(30, 8, self.checkHostStateInCloudstack, "Up", hostToTest.id)
if not (hostUpInCloudstack):
raise self.fail("Host is not up %s, in cloudstack so failing test " % (hostToTest.ipaddress))
return

View File

@ -170,7 +170,10 @@
<router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link> <router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link>
</template> </template>
<template v-if="column.key === 'size'"> <template v-if="column.key === 'size'">
<span v-if="text"> <span v-if="text && $route.path === '/kubernetes'">
{{ text }}
</span>
<span v-else-if="text">
{{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB {{ parseFloat(parseFloat(text) / 1024.0 / 1024.0 / 1024.0).toFixed(2) }} GiB
</span> </span>
</template> </template>
@ -187,17 +190,18 @@
</template> </template>
<template v-if="column.key === 'hypervisor'"> <template v-if="column.key === 'hypervisor'">
<span v-if="$route.name === 'hypervisorcapability'"> <span v-if="$route.name === 'hypervisorcapability'">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link> <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</span> </span>
<span v-else-if="$route.name === 'guestoshypervisormapping'"> <span v-else-if="$route.name === 'guestoshypervisormapping'">
<QuickView <QuickView
style="margin-left: 5px" style="margin-left: 5px"
:actions="actions" :actions="actions"
:resource="record" :resource="record"
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'hypervisor' " :enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'hypervisor' "
@exec-action="$parent.execAction"/> @exec-action="$parent.execAction"/>
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link> <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
</span> </span>
</template>
<span v-else>{{ text }}</span> <span v-else>{{ text }}</span>
</template> </template>
<template v-if="column.key === 'osname'"> <template v-if="column.key === 'osname'">
@ -268,8 +272,6 @@
</span> </span>
</span> </span>
</template> </template>
</template>
<template v-if="column.key === 'level'"> <template v-if="column.key === 'level'">
<router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link> <router-link :to="{ path: '/event/' + record.id }">{{ text }}</router-link>
</template> </template>