Skip to content

Commit d3e020a

Browse files
sureshanapartiMarcus Sorensen
andauthored
Mark libvirt events experimental, add properties flag (#8825)
* Mark libvirt events experimental, add properties flag * unit test fixes --------- Co-authored-by: Marcus Sorensen <mls@apple.com>
1 parent 730cc5d commit d3e020a

8 files changed

Lines changed: 104 additions & 60 deletions

File tree

agent/conf/agent.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,7 @@ iscsi.session.cleanup.enabled=false
426426

427427
# Instance Conversion from Vmware to KVM through virt-v2v. Enable verbose mode
428428
# virtv2v.verbose.enabled=false
429+
430+
# If set to "true", the agent will register for libvirt domain events, allowing for immediate updates on crashed or
431+
# unexpectedly stopped. Experimental, requires agent restart.
432+
# libvirt.events.enabled=false

agent/src/main/java/com/cloud/agent/properties/AgentProperties.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,13 @@ public class AgentProperties{
695695
*/
696696
public static final Property<Boolean> DEVELOPER = new Property<>("developer", false);
697697

698+
/**
699+
* If set to "true", the agent will register for libvirt domain events, allowing for immediate updates on crashed or unexpectedly
700+
* stopped VMs. Experimental, requires agent restart.
701+
* Default value: <code>false</code>
702+
*/
703+
public static final Property<Boolean> LIBVIRT_EVENTS_ENABLED = new Property<>("libvirt.events.enabled", false);
704+
698705
/**
699706
* Can only be used if developer = true. This property is used to define the local bridge name and private network name.<br>
700707
* Data type: String.<br>

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@
9292
import org.libvirt.SchedUlongParameter;
9393
import org.libvirt.Secret;
9494
import org.libvirt.VcpuInfo;
95-
import org.libvirt.event.DomainEvent;
96-
import org.libvirt.event.DomainEventDetail;
97-
import org.libvirt.event.StoppedDetail;
9895
import org.w3c.dom.Document;
9996
import org.w3c.dom.Element;
10097
import org.w3c.dom.Node;
@@ -468,7 +465,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
468465
protected CPUStat cpuStat = new CPUStat();
469466
protected MemStat memStat = new MemStat(dom0MinMem, dom0OvercommitMem);
470467
private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
471-
private AgentStatusUpdater _agentStatusUpdater;
468+
private LibvirtDomainListener libvirtDomainListener;
472469

473470
protected Boolean enableManuallySettingCpuTopologyOnKvmVm = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM);
474471

@@ -502,8 +499,23 @@ protected long getHypervisorQemuVersion() {
502499
}
503500

504501
@Override
505-
public void registerStatusUpdater(AgentStatusUpdater updater) {
506-
_agentStatusUpdater = updater;
502+
public synchronized void registerStatusUpdater(AgentStatusUpdater updater) {
503+
if (AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED)) {
504+
try {
505+
Connect conn = LibvirtConnection.getConnection();
506+
if (libvirtDomainListener != null) {
507+
s_logger.debug("Clearing old domain listener");
508+
conn.removeLifecycleListener(libvirtDomainListener);
509+
}
510+
libvirtDomainListener = new LibvirtDomainListener(updater);
511+
conn.addLifecycleListener(libvirtDomainListener);
512+
s_logger.debug("Set up the libvirt domain event lifecycle listener");
513+
} catch (LibvirtException e) {
514+
s_logger.error("Failed to get libvirt connection for domain event lifecycle", e);
515+
}
516+
} else {
517+
s_logger.debug("Libvirt event listening is disabled, not registering status updater");
518+
}
507519
}
508520

509521
@Override
@@ -1878,6 +1890,10 @@ public String startVM(final Connect conn, final String vmName, final String doma
18781890
public boolean stop() {
18791891
try {
18801892
final Connect conn = LibvirtConnection.getConnection();
1893+
if (AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED) && libvirtDomainListener != null) {
1894+
s_logger.debug("Clearing old domain listener");
1895+
conn.removeLifecycleListener(libvirtDomainListener);
1896+
}
18811897
conn.close();
18821898
} catch (final LibvirtException e) {
18831899
s_logger.trace("Ignoring libvirt error.", e);
@@ -3688,50 +3704,9 @@ private StartupStorageCommand createLocalStoragePool(String localStoragePath, St
36883704
} catch (final CloudRuntimeException e) {
36893705
s_logger.debug("Unable to initialize local storage pool: " + e);
36903706
}
3691-
setupLibvirtEventListener();
36923707
return sscmd;
36933708
}
36943709

3695-
private void setupLibvirtEventListener() {
3696-
try {
3697-
Connect conn = LibvirtConnection.getConnection();
3698-
conn.addLifecycleListener(this::onDomainLifecycleChange);
3699-
3700-
s_logger.debug("Set up the libvirt domain event lifecycle listener");
3701-
} catch (LibvirtException e) {
3702-
s_logger.error("Failed to get libvirt connection for domain event lifecycle", e);
3703-
}
3704-
}
3705-
3706-
private int onDomainLifecycleChange(Domain domain, DomainEvent domainEvent) {
3707-
try {
3708-
s_logger.debug(String.format("Got event lifecycle change on Domain %s, event %s", domain.getName(), domainEvent));
3709-
if (domainEvent != null) {
3710-
switch (domainEvent.getType()) {
3711-
case STOPPED:
3712-
/* libvirt-destroyed VMs have detail StoppedDetail.DESTROYED, self shutdown guests are StoppedDetail.SHUTDOWN
3713-
* Checking for this helps us differentiate between events where cloudstack or admin stopped the VM vs guest
3714-
* initiated, and avoid pushing extra updates for actions we are initiating without a need for extra tracking */
3715-
DomainEventDetail detail = domainEvent.getDetail();
3716-
if (StoppedDetail.SHUTDOWN.equals(detail) || StoppedDetail.CRASHED.equals(detail) || StoppedDetail.FAILED.equals(detail)) {
3717-
s_logger.info("Triggering out of band status update due to completed self-shutdown or crash of VM");
3718-
_agentStatusUpdater.triggerUpdate();
3719-
} else {
3720-
s_logger.debug("Event detail: " + detail);
3721-
}
3722-
break;
3723-
default:
3724-
s_logger.debug(String.format("No handling for event %s", domainEvent));
3725-
}
3726-
}
3727-
} catch (LibvirtException e) {
3728-
s_logger.error("Libvirt exception while processing lifecycle event", e);
3729-
} catch (Throwable e) {
3730-
s_logger.error("Error during lifecycle", e);
3731-
}
3732-
return 0;
3733-
}
3734-
37353710
public String diskUuidToSerial(String uuid) {
37363711
String uuidWithoutHyphen = uuid.replace("-","");
37373712
return uuidWithoutHyphen.substring(0, Math.min(uuidWithoutHyphen.length(), 20));

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static public Connect getConnection() throws LibvirtException {
4141
return getConnection(s_hypervisorURI);
4242
}
4343

44-
static public Connect getConnection(String hypervisorURI) throws LibvirtException {
44+
static synchronized public Connect getConnection(String hypervisorURI) throws LibvirtException {
4545
s_logger.debug("Looking for libvirtd connection at: " + hypervisorURI);
4646
Connect conn = s_connections.get(hypervisorURI);
4747

@@ -121,6 +121,11 @@ static String getHypervisorURI(String hypervisorType) {
121121
* @throws LibvirtException
122122
*/
123123
private static synchronized void setupEventListener() throws LibvirtException {
124+
if (!AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LIBVIRT_EVENTS_ENABLED)) {
125+
s_logger.debug("Libvirt event listening is disabled, not setting up event loop");
126+
return;
127+
}
128+
124129
if (libvirtEventThread == null || !libvirtEventThread.isAlive()) {
125130
// Registers a default event loop, must be called before connecting to hypervisor
126131
Library.initEventLoop();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package com.cloud.hypervisor.kvm.resource;
16+
17+
import com.cloud.resource.AgentStatusUpdater;
18+
import org.apache.log4j.Logger;
19+
import org.libvirt.Domain;
20+
import org.libvirt.LibvirtException;
21+
import org.libvirt.event.DomainEvent;
22+
import org.libvirt.event.DomainEventDetail;
23+
import org.libvirt.event.LifecycleListener;
24+
import org.libvirt.event.StoppedDetail;
25+
26+
public class LibvirtDomainListener implements LifecycleListener {
27+
private static final Logger LOGGER = Logger.getLogger(LibvirtDomainListener.class);
28+
29+
private final AgentStatusUpdater agentStatusUpdater;
30+
31+
public LibvirtDomainListener(AgentStatusUpdater updater) {
32+
agentStatusUpdater = updater;
33+
}
34+
35+
public int onLifecycleChange(Domain domain, DomainEvent domainEvent) {
36+
try {
37+
LOGGER.debug(String.format("Got event lifecycle change on Domain %s, event %s", domain.getName(), domainEvent));
38+
if (domainEvent != null) {
39+
switch (domainEvent.getType()) {
40+
case STOPPED:
41+
/* libvirt-destroyed VMs have detail StoppedDetail.DESTROYED, self shutdown guests are StoppedDetail.SHUTDOWN
42+
* Checking for this helps us differentiate between events where cloudstack or admin stopped the VM vs guest
43+
* initiated, and avoid pushing extra updates for actions we are initiating without a need for extra tracking */
44+
DomainEventDetail detail = domainEvent.getDetail();
45+
if (StoppedDetail.SHUTDOWN.equals(detail) || StoppedDetail.CRASHED.equals(detail) || StoppedDetail.FAILED.equals(detail)) {
46+
if (agentStatusUpdater != null) {
47+
LOGGER.info("Triggering out of band status update due to completed self-shutdown or crash of VM");
48+
agentStatusUpdater.triggerUpdate();
49+
}
50+
} else {
51+
LOGGER.debug("Event detail: " + detail);
52+
}
53+
break;
54+
default:
55+
LOGGER.debug(String.format("No handling for event %s", domainEvent));
56+
}
57+
}
58+
} catch (LibvirtException e) {
59+
LOGGER.error("Libvirt exception while processing lifecycle event", e);
60+
} catch (Throwable e) {
61+
LOGGER.error("Error during lifecycle", e);
62+
}
63+
return 0;
64+
}
65+
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,6 @@ public Answer execute(ScaleVmCommand command, LibvirtComputingResource libvirtCo
5959
String message = String.format("Unable to scale %s due to [%s].", scalingDetails, e.getMessage());
6060
logger.error(message, e);
6161
return new ScaleVmAnswer(command, false, message);
62-
} finally {
63-
if (conn != null) {
64-
try {
65-
conn.close();
66-
} catch (LibvirtException ex) {
67-
logger.warn(String.format("Error trying to close libvirt connection [%s]", ex.getMessage()), ex);
68-
}
69-
}
7062
}
7163
}
7264

plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/KVMHostInfo.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ private void getHostInfoFromLibvirt() {
222222
We used to check if this was supported, but that is no longer required
223223
*/
224224
this.capabilities.add("snapshot");
225-
conn.close();
226225
} catch (final LibvirtException e) {
227226
LOGGER.error("Caught libvirt exception while fetching host information", e);
228227
}

plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/linux/KVMHostInfoTest.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ public void manualCpuSpeedTest() throws Exception {
7070
Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
7171
Mockito.when(conn.nodeInfo()).thenReturn(nodeInfo);
7272
Mockito.when(conn.getCapabilities()).thenReturn(capabilitiesXml);
73-
Mockito.when(conn.close()).thenReturn(0);
7473
int manualSpeed = 500;
7574

7675
KVMHostInfo kvmHostInfo = new KVMHostInfo(10, 10, manualSpeed, 0);
@@ -92,8 +91,6 @@ public void reservedCpuCoresTest() throws Exception {
9291
Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
9392
Mockito.when(conn.nodeInfo()).thenReturn(nodeInfo);
9493
Mockito.when(conn.getCapabilities()).thenReturn(capabilitiesXml);
95-
Mockito.when(conn.close()).thenReturn(0);
96-
int manualSpeed = 500;
9794

9895
KVMHostInfo kvmHostInfo = new KVMHostInfo(10, 10, 100, 2);
9996
Assert.assertEquals("reserve two CPU cores", 8, kvmHostInfo.getAllocatableCpus());

0 commit comments

Comments
 (0)