Skip to content

Commit 1e930ea

Browse files
committed
CLOUDSTACK-8715: Add VirtIO channel to all Instances for the Qemu Guest Agent
This commit adds a additional VirtIO channel with the name 'org.qemu.guest_agent.0' to all Instances. With the Qemu Guest Agent the Hypervisor gains more control over the Instance if these tools are present inside the Instance, for example: * Power control * Flushing filesystems In the future this should allow safer snapshots on KVM since we can instruct the Instance to flush the filesystems prior to snapshotting the disk. More information: http://wiki.qemu.org/Features/QAPI/GuestAgent Keep in mind that on Ubuntu AppArmor still needs to be disabled since the default AppArmor profile doesn't allow libvirt to write into /var/lib/libvirt/qemu
1 parent e762e27 commit 1e930ea

File tree

10 files changed

+302
-12
lines changed

10 files changed

+302
-12
lines changed

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

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
import javax.ejb.Local;
4545
import javax.naming.ConfigurationException;
4646

47+
import org.apache.cloudstack.api.guestagent.GuestAgentAnswer;
48+
import org.apache.cloudstack.api.guestagent.GuestAgentAnswer.GuestAgentIntegerAnswer;
49+
import org.apache.cloudstack.api.guestagent.GuestAgentCommand;
4750
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
4851
import org.apache.cloudstack.storage.to.VolumeObjectTO;
4952
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
@@ -92,6 +95,7 @@
9295
import com.cloud.exception.InternalErrorException;
9396
import com.cloud.host.Host.Type;
9497
import com.cloud.hypervisor.Hypervisor.HypervisorType;
98+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
9599
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef;
96100
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef;
97101
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuModeDef;
@@ -111,7 +115,6 @@
111115
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef;
112116
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TermPolicy;
113117
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef;
114-
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VirtioSerialDef;
115118
import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtRequestWrapper;
116119
import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper;
117120
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
@@ -142,6 +145,8 @@
142145
import com.cloud.utils.ssh.SshHelper;
143146
import com.cloud.vm.VirtualMachine;
144147
import com.cloud.vm.VirtualMachine.PowerState;
148+
import com.google.gson.Gson;
149+
import com.google.gson.GsonBuilder;
145150

146151
/**
147152
* LibvirtComputingResource execute requests on the computing/routing host using
@@ -1982,9 +1987,14 @@ So if getMinSpeed() returns null we fall back to getSpeed().
19821987
final SerialDef serial = new SerialDef("pty", null, (short)0);
19831988
devices.addDevice(serial);
19841989

1990+
/* Add a VirtIO channel for the Qemu Guest Agent tools */
1991+
devices.addDevice(new ChannelDef("org.qemu.guest_agent.0", ChannelDef.ChannelType.UNIX,
1992+
"/var/lib/libvirt/qemu/" + vmTO.getName() + ".org.qemu.guest_agent.0"));
1993+
1994+
/* Add a VirtIO channel for SystemVMs for communication and provisioning */
19851995
if (vmTO.getType() != VirtualMachine.Type.User) {
1986-
final VirtioSerialDef vserial = new VirtioSerialDef(vmTO.getName(), null);
1987-
devices.addDevice(vserial);
1996+
devices.addDevice(new ChannelDef(vmTO.getName() + ".vport", ChannelDef.ChannelType.UNIX,
1997+
"/var/lib/libvirt/qemu/" + vmTO.getName() + ".agent"));
19881998
}
19891999

19902000
final VideoDef videoCard = new VideoDef(_videoHw, _videoRam);
@@ -3406,4 +3416,50 @@ public String mapRbdDevice(final KVMPhysicalDisk disk){
34063416
}
34073417
return device;
34083418
}
3419+
3420+
public <A extends GuestAgentAnswer> A SendToVMAgent(Connect conn, String vmName, GuestAgentCommand cmd, Class<?> answerClass, Integer timeout) {
3421+
Domain dm = null;
3422+
Gson gson = new GsonBuilder().create();
3423+
try {
3424+
dm = conn.domainLookupByName(vmName);
3425+
String result = dm.qemuAgentCommand(gson.toJson(cmd), timeout, 0);
3426+
return (A) gson.fromJson(result, answerClass);
3427+
} catch (LibvirtException e) {
3428+
s_logger.warn("Failed to send Qemu Guest command to Instance " + vmName + " due to: " + e.getMessage());
3429+
} finally {
3430+
try {
3431+
if (dm != null) {
3432+
dm.free();
3433+
}
3434+
} catch (LibvirtException l) {
3435+
s_logger.trace("Ignoring libvirt error.", l);
3436+
}
3437+
}
3438+
return null;
3439+
}
3440+
3441+
public boolean checkGuestAgentSync(Connect conn, String vmName) {
3442+
HashMap arguments = new HashMap();
3443+
int id = (int)(Math.random() * 1000000) + 1;
3444+
arguments.put("id", id);
3445+
GuestAgentCommand cmd = new GuestAgentCommand("guest-sync", arguments);
3446+
GuestAgentIntegerAnswer answer = null;
3447+
try {
3448+
answer = (GuestAgentIntegerAnswer) SendToVMAgent(conn, vmName, cmd, GuestAgentIntegerAnswer.class, 5);
3449+
} catch (Exception e) {
3450+
s_logger.warn("Failed to send guest-sync command to guest agent: " + e.getMessage());
3451+
return false;
3452+
}
3453+
3454+
if (answer == null) {
3455+
s_logger.warn("Answer from guest agent in " + vmName + " is null");
3456+
return false;
3457+
} else if (answer.getAnswer() != id) {
3458+
s_logger.warn("Answer from guest agent in " + vmName + " is different from input value");
3459+
return false;
3460+
} else {
3461+
s_logger.debug("id = " + id + ", answer from guest agent: " + answer.getAnswer());
3462+
return true;
3463+
}
3464+
}
34093465
}

plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.xml.sax.InputSource;
3434
import org.xml.sax.SAXException;
3535

36+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
3637
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
3738
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
3839
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.NicModel;
@@ -41,6 +42,7 @@ public class LibvirtDomainXMLParser {
4142
private static final Logger s_logger = Logger.getLogger(LibvirtDomainXMLParser.class);
4243
private final List<InterfaceDef> interfaces = new ArrayList<InterfaceDef>();
4344
private final List<DiskDef> diskDefs = new ArrayList<DiskDef>();
45+
private final List<ChannelDef> channels = new ArrayList<ChannelDef>();
4446
private Integer vncPort;
4547
private String desc;
4648

@@ -171,6 +173,25 @@ public boolean parseDomainXML(String domXML) {
171173
interfaces.add(def);
172174
}
173175

176+
NodeList ports = devices.getElementsByTagName("channel");
177+
for (int i = 0; i < ports.getLength(); i++) {
178+
Element channel = (Element)ports.item(i);
179+
180+
String type = channel.getAttribute("type");
181+
String path = getAttrValue("source", "path", channel);
182+
String name = getAttrValue("target", "name", channel);
183+
String state = getAttrValue("target", "state", channel);
184+
185+
ChannelDef def = null;
186+
if (state == null || state.length() == 0) {
187+
def = new ChannelDef(name, ChannelDef.ChannelType.valueOf(type.toUpperCase()), path);
188+
} else {
189+
def = new ChannelDef(name, ChannelDef.ChannelType.valueOf(type.toUpperCase()), ChannelDef.ChannelState.valueOf(state.toUpperCase()), path);
190+
}
191+
192+
channels.add(def);
193+
}
194+
174195
Element graphic = (Element)devices.getElementsByTagName("graphics").item(0);
175196

176197
if (graphic != null) {
@@ -234,6 +255,10 @@ public List<DiskDef> getDisks() {
234255
return diskDefs;
235256
}
236257

258+
public List<ChannelDef> getChannels() {
259+
return channels;
260+
}
261+
237262
public String getDescription() {
238263
return desc;
239264
}

plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,25 +1209,95 @@ public String toString() {
12091209
}
12101210
}
12111211

1212-
public static class VirtioSerialDef {
1213-
private final String _name;
1212+
public static class ChannelDef {
1213+
enum ChannelType {
1214+
UNIX("unix"), SERIAL("serial");
1215+
String _type;
1216+
1217+
ChannelType(String type) {
1218+
_type = type;
1219+
}
1220+
1221+
@Override
1222+
public String toString() {
1223+
return _type;
1224+
}
1225+
}
1226+
1227+
enum ChannelState {
1228+
DISCONNECTED("disconnected"), CONNECTED("connected");
1229+
String _type;
1230+
1231+
ChannelState(String type) {
1232+
_type = type;
1233+
}
1234+
1235+
@Override
1236+
public String toString() {
1237+
return _type;
1238+
}
1239+
}
1240+
1241+
private String _name;
12141242
private String _path;
1243+
private ChannelType _type;
1244+
private ChannelState _state;
1245+
1246+
public ChannelDef(String name, ChannelType type) {
1247+
_name = name;
1248+
_type = type;
1249+
}
1250+
1251+
public ChannelDef(String name, ChannelType type, String path) {
1252+
_name = name;
1253+
_path = path;
1254+
_type = type;
1255+
}
1256+
1257+
public ChannelDef(String name, ChannelType type, ChannelState state) {
1258+
_name = name;
1259+
_state = state;
1260+
_type = type;
1261+
}
12151262

1216-
public VirtioSerialDef(String name, String path) {
1263+
public ChannelDef(String name, ChannelType type, ChannelState state, String path) {
12171264
_name = name;
12181265
_path = path;
1266+
_state = state;
1267+
_type = type;
1268+
}
1269+
1270+
public ChannelType getChannelType() {
1271+
return _type;
1272+
}
1273+
1274+
public ChannelState getChannelState() {
1275+
return _state;
1276+
}
1277+
1278+
public String getName() {
1279+
return _name;
1280+
}
1281+
1282+
public String getPath() {
1283+
return _path;
12191284
}
12201285

12211286
@Override
12221287
public String toString() {
12231288
StringBuilder virtioSerialBuilder = new StringBuilder();
1289+
virtioSerialBuilder.append("<channel type='" + _type.toString() + "'>\n");
12241290
if (_path == null) {
1225-
_path = "/var/lib/libvirt/qemu";
1291+
virtioSerialBuilder.append("<source mode='bind'/>\n");
1292+
} else {
1293+
virtioSerialBuilder.append("<source mode='bind' path='" + _path + "'/>\n");
12261294
}
1227-
virtioSerialBuilder.append("<channel type='unix'>\n");
1228-
virtioSerialBuilder.append("<source mode='bind' path='" + _path + "/" + _name + ".agent'/>\n");
1229-
virtioSerialBuilder.append("<target type='virtio' name='" + _name + ".vport'/>\n");
12301295
virtioSerialBuilder.append("<address type='virtio-serial'/>\n");
1296+
if (_state == null) {
1297+
virtioSerialBuilder.append("<target type='virtio' name='" + _name + "'/>\n");
1298+
} else {
1299+
virtioSerialBuilder.append("<target type='virtio' name='" + _name + "' state='" + _state.toString() + "'/>\n");
1300+
}
12311301
virtioSerialBuilder.append("</channel>\n");
12321302
return virtioSerialBuilder.toString();
12331303
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.apache.cloudstack.api.guestagent;
2+
3+
import com.google.gson.annotations.SerializedName;
4+
5+
public class GuestAgentAnswer {
6+
7+
public static class GuestAgentIntegerAnswer extends GuestAgentAnswer {
8+
@SerializedName("return")
9+
int answer;
10+
11+
public int getAnswer() {
12+
return answer;
13+
}
14+
}
15+
16+
public static class GuestAgentStringAnswer extends GuestAgentAnswer {
17+
@SerializedName("return")
18+
String answer;
19+
20+
public String getAnswer() {
21+
return answer;
22+
}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.apache.cloudstack.api.guestagent;
2+
3+
import java.util.HashMap;
4+
5+
public class GuestAgentCommand {
6+
String execute;
7+
@SuppressWarnings("rawtypes")
8+
HashMap arguments;
9+
10+
@SuppressWarnings("rawtypes")
11+
public GuestAgentCommand(String execute, HashMap arguments) {
12+
this.execute = execute;
13+
this.arguments = arguments;
14+
}
15+
16+
public String getCommand() {
17+
return execute;
18+
}
19+
20+
@SuppressWarnings("rawtypes")
21+
public HashMap getArguments() {
22+
return arguments;
23+
}
24+
}

plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
148148
import com.cloud.exception.InternalErrorException;
149149
import com.cloud.hypervisor.kvm.resource.KVMHABase.NfsStoragePool;
150+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
150151
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
151152
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
152153
import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtRequestWrapper;
@@ -333,6 +334,9 @@ private void verifyVm(final VirtualMachineTO to, final LibvirtVMDef vm) {
333334
assertXpath(domainDoc, "/domain/devices/input/@type", "tablet");
334335
assertXpath(domainDoc, "/domain/devices/input/@bus", "usb");
335336

337+
assertNodeExists(domainDoc, "/domain/devices/channel");
338+
assertXpath(domainDoc, "/domain/devices/channel/@type", ChannelDef.ChannelType.UNIX.toString());
339+
336340
assertXpath(domainDoc, "/domain/memory/text()", String.valueOf( to.getMaxRam() / 1024 ));
337341
assertXpath(domainDoc, "/domain/currentMemory/text()", String.valueOf( to.getMinRam() / 1024 ));
338342

plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
2525
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
26+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
2627

2728
public class LibvirtDomainXMLParserTest extends TestCase {
2829

@@ -38,6 +39,11 @@ public void testDomainXMLParser() {
3839
InterfaceDef.NicModel ifModel = InterfaceDef.NicModel.VIRTIO;
3940
InterfaceDef.GuestNetType ifType = InterfaceDef.GuestNetType.BRIDGE;
4041

42+
ChannelDef.ChannelType channelType = ChannelDef.ChannelType.UNIX;
43+
ChannelDef.ChannelState channelState = ChannelDef.ChannelState.DISCONNECTED;
44+
String guestAgentPath = "/var/lib/libvirt/qemu/guest-agent.org.qemu.guest_agent.0";
45+
String guestAgentName = "org.qemu.guest_agent.0";
46+
4147
String diskLabel ="vda";
4248
String diskPath = "/var/lib/libvirt/images/my-test-image.qcow2";
4349

@@ -144,7 +150,7 @@ public void testDomainXMLParser() {
144150
"</console>" +
145151
"<channel type='unix'>" +
146152
"<source mode='bind' path='/var/lib/libvirt/qemu/s-2970-VM.agent'/>" +
147-
"<target type='virtio' name='s-2970-VM.vport'/>" +
153+
"<target type='virtio' name='s-2970-VM.vport' state='disconnected'/>" +
148154
"<alias name='channel0'/>" +
149155
"<address type='virtio-serial' controller='0' bus='0' port='1'/>" +
150156
"</channel>" +
@@ -164,6 +170,12 @@ public void testDomainXMLParser() {
164170
"<alias name='balloon0'/>" +
165171
"<address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>" +
166172
"</memballoon>" +
173+
"<channel type='unix'>" +
174+
"<source mode='bind' path='" + guestAgentPath + "'/>" +
175+
"<target type='virtio' name='" + guestAgentName + "'/>" +
176+
"<alias name='channel0'/>" +
177+
"<address type='virtio-serial' controller='0' bus='0' port='1'/>" +
178+
"</channel>" +
167179
"</devices>" +
168180
"<seclabel type='none'/>" +
169181
"</domain>";
@@ -185,6 +197,16 @@ public void testDomainXMLParser() {
185197
assertEquals(deviceType, disks.get(diskId).getDeviceType());
186198
assertEquals(diskFormat, disks.get(diskId).getDiskFormatType());
187199

200+
List<ChannelDef> channels = parser.getChannels();
201+
for (int i = 0; i < channels.size(); i++) {
202+
assertEquals(channelType, channels.get(i).getChannelType());
203+
assertEquals(channelType, channels.get(i).getChannelType());
204+
}
205+
206+
assertEquals(channelState, channels.get(0).getChannelState());
207+
assertEquals(guestAgentPath, channels.get(1).getPath());
208+
assertEquals(guestAgentName, channels.get(1).getName());
209+
188210
List<InterfaceDef> ifs = parser.getInterfaces();
189211
for (int i = 0; i < ifs.size(); i++) {
190212
assertEquals(ifModel, ifs.get(i).getModel());

0 commit comments

Comments
 (0)