Skip to content

Commit 01e1bba

Browse files
Allow selecting guest OS for KVM imports
1 parent 4425ee4 commit 01e1bba

9 files changed

Lines changed: 245 additions & 29 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.cloudstack.api.ServerApiException;
3333
import org.apache.cloudstack.api.response.ClusterResponse;
3434
import org.apache.cloudstack.api.response.DomainResponse;
35+
import org.apache.cloudstack.api.response.GuestOSResponse;
3536
import org.apache.cloudstack.api.response.ProjectResponse;
3637
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
3738
import org.apache.cloudstack.api.response.TemplateResponse;
@@ -118,6 +119,13 @@ public class ImportUnmanagedInstanceCmd extends BaseAsyncCmd {
118119
description = "The ID of the Template for the Instance")
119120
private Long templateId;
120121

122+
@Parameter(name = ApiConstants.OS_ID,
123+
type = CommandType.UUID,
124+
entityType = GuestOSResponse.class,
125+
since = "4.22.1",
126+
description = "optional - the ID of the guest OS for the imported Instance.")
127+
private Long guestOsId;
128+
121129
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
122130
type = CommandType.UUID,
123131
entityType = ServiceOfferingResponse.class,
@@ -187,6 +195,10 @@ public Long getTemplateId() {
187195
return templateId;
188196
}
189197

198+
public Long getGuestOsId() {
199+
return guestOsId;
200+
}
201+
190202
public Long getProjectId() {
191203
return projectId;
192204
}

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.apache.cloudstack.api.Parameter;
3131
import org.apache.cloudstack.api.ResponseObject;
3232
import org.apache.cloudstack.api.ServerApiException;
33-
import org.apache.cloudstack.api.response.GuestOSResponse;
3433
import org.apache.cloudstack.api.response.HostResponse;
3534
import org.apache.cloudstack.api.response.NetworkResponse;
3635
import org.apache.cloudstack.api.response.StoragePoolResponse;
@@ -107,6 +106,18 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
107106
description = "the network ID")
108107
private Long networkId;
109108

109+
@Parameter(name = ApiConstants.MAC_ADDRESS,
110+
type = CommandType.STRING,
111+
since = "4.22.1",
112+
description = "(only for importing VMs from KVM local/shared storage) optional - the MAC address for the imported VM NIC. If omitted, a new MAC address is generated.")
113+
private String macAddress;
114+
115+
@Parameter(name = ApiConstants.IP_ADDRESS,
116+
type = CommandType.STRING,
117+
since = "4.22.1",
118+
description = "(only for importing VMs from KVM local/shared storage) optional - the IPv4 address for the imported VM NIC. If omitted, IPv4 assignment remains automatic.")
119+
private String ipAddress;
120+
110121
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "Host where local disk is located")
111122
private Long hostId;
112123

@@ -172,13 +183,6 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
172183
description = "(only for importing VMs from VMware to KVM) optional - if true, forces virt-v2v conversions to write directly on the provided storage pool (avoid using temporary conversion pool).")
173184
private Boolean forceConvertToPool;
174185

175-
@Parameter(name = ApiConstants.OS_ID,
176-
type = CommandType.UUID,
177-
entityType = GuestOSResponse.class,
178-
since = "4.22.1",
179-
description = "(only for importing VMs from VMware to KVM) optional - the ID of the guest OS for the imported VM.")
180-
private Long guestOsId;
181-
182186
@Parameter(name = ApiConstants.USE_VDDK,
183187
type = CommandType.BOOLEAN,
184188
since = "4.22.1",
@@ -275,6 +279,14 @@ public Long getNetworkId() {
275279
return networkId;
276280
}
277281

282+
public String getMacAddress() {
283+
return macAddress;
284+
}
285+
286+
public String getIpAddress() {
287+
return ipAddress;
288+
}
289+
278290
@Override
279291
public String getEventType() {
280292
return EventTypes.EVENT_VM_IMPORT;
@@ -288,10 +300,6 @@ public boolean getForceConvertToPool() {
288300
return BooleanUtils.toBooleanDefaultIfNull(forceConvertToPool, false);
289301
}
290302

291-
public Long getGuestOsId() {
292-
return guestOsId;
293-
}
294-
295303
@Override
296304
public String getEventDescription() {
297305
String vmName = getName();

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class LibvirtDomainXMLParser {
6161
private Integer vncPort;
6262
private String vncPasswd;
6363
private String desc;
64+
private String osInfoId;
6465
private LibvirtVMDef.CpuTuneDef cpuTuneDef;
6566
private LibvirtVMDef.CpuModeDef cpuModeDef;
6667
private String name;
@@ -79,6 +80,7 @@ public boolean parseDomainXML(String domXML) {
7980
Element rootElement = doc.getDocumentElement();
8081

8182
desc = getTagValue("description", rootElement);
83+
osInfoId = getOsInfoId(rootElement);
8284
name = getTagValue("name", rootElement);
8385

8486
Element devices = (Element)rootElement.getElementsByTagName("devices").item(0);
@@ -455,6 +457,27 @@ private static String getAttrValue(String tag, String attr, Element eElement) {
455457
return node.getAttribute(attr);
456458
}
457459

460+
private static String getOsInfoId(Element rootElement) {
461+
if (rootElement == null) {
462+
return null;
463+
}
464+
465+
NodeList nodes = rootElement.getElementsByTagName("*");
466+
for (int i = 0; i < nodes.getLength(); i++) {
467+
Node node = nodes.item(i);
468+
String nodeName = node.getNodeName();
469+
String namespace = node.getNamespaceURI();
470+
if ("libosinfo:os".equals(nodeName) || ("os".equals(node.getLocalName()) && StringUtils.contains(namespace, "libosinfo"))) {
471+
Element element = (Element)node;
472+
String id = element.getAttribute("id");
473+
if (StringUtils.isNotBlank(id)) {
474+
return id;
475+
}
476+
}
477+
}
478+
return null;
479+
}
480+
458481
/**
459482
* Parse the disk block part of the libvirt XML.
460483
* @param def
@@ -510,6 +533,10 @@ public String getDescription() {
510533
return desc;
511534
}
512535

536+
public String getOsInfoId() {
537+
return osInfoId;
538+
}
539+
513540
public String getName() {
514541
return name;
515542
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvir
9898
if (parser.getCpuTuneDef() !=null) {
9999
instance.setCpuSpeed(parser.getCpuTuneDef().getShares());
100100
}
101+
instance.setOperatingSystemId(parser.getOsInfoId());
102+
instance.setOperatingSystem(parser.getDescription());
101103
instance.setHypervisorType(Hypervisor.HypervisorType.KVM.name());
102104
instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName())));
103105
instance.setNics(getUnmanagedInstanceNics(parser.getInterfaces()));

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ private UnmanagedInstanceTO getUnmanagedInstance(LibvirtComputingResource libvir
131131
if (parser.getCpuModeDef() != null) {
132132
instance.setCpuCoresPerSocket(parser.getCpuModeDef().getCoresPerSocket());
133133
}
134+
instance.setOperatingSystemId(parser.getOsInfoId());
135+
instance.setOperatingSystem(parser.getDescription());
134136
instance.setHypervisorType(Hypervisor.HypervisorType.KVM.name());
135137
instance.setPowerState(getPowerState(libvirtComputingResource.getVmState(conn,domain.getName())));
136138
instance.setMemory((int) LibvirtComputingResource.getDomainMemory(domain) / 1024);

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,17 @@ public void testDomainXMLParser() {
7373
String diskPath2 = "/var/lib/libvirt/images/my-test-image2.qcow2";
7474
String secretUuid = "5644d664-a238-3a9b-811c-961f609d29f4";
7575

76-
String xml = "<domain type='kvm' id='10'>" +
76+
String osInfoId = "http://ubuntu.com/ubuntu/24.04";
77+
78+
String xml = "<domain type='kvm' id='10' xmlns:libosinfo='http://libosinfo.org/xmlns/libvirt/domain/1.0'>" +
7779
"<name>s-2970-VM</name>" +
7880
"<uuid>4d2c1526-865d-4fc9-a1ac-dbd1801a22d0</uuid>" +
7981
"<description>Debian GNU/Linux 6(64-bit)</description>" +
82+
"<metadata>" +
83+
"<libosinfo:libosinfo>" +
84+
"<libosinfo:os id='" + osInfoId + "'/>" +
85+
"</libosinfo:libosinfo>" +
86+
"</metadata>" +
8087
"<memory unit='KiB'>262144</memory>" +
8188
"<currentMemory unit='KiB'>262144</currentMemory>" +
8289
"<vcpu placement='static'>1</vcpu>" +
@@ -220,6 +227,7 @@ public void testDomainXMLParser() {
220227
parser.parseDomainXML(xml);
221228

222229
assertEquals(vncPort - 5900, (int)parser.getVncPort());
230+
assertEquals(osInfoId, parser.getOsInfoId());
223231

224232
List<DiskDef> disks = parser.getDisks();
225233
/* Disk 0 is the first disk, the QCOW2 file backed virto disk */

server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,7 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
13191319
final Map<String, Long> dataDiskOfferingMap = cmd.getDataDiskToDiskOfferingList();
13201320
final Map<String, String> details = cmd.getDetails();
13211321
final boolean forced = cmd.isForced();
1322+
Long guestOsId = cmd.getGuestOsId();
13221323
List<HostVO> hosts = resourceManager.listHostsInClusterByStatus(clusterId, Status.Up);
13231324
UserVm userVm = null;
13241325
List<String> additionalNameFilters = getAdditionalNameFilters(cluster);
@@ -1350,7 +1351,7 @@ private UserVmResponse baseImportInstance(ImportUnmanagedInstanceCmd cmd) {
13501351
template, instanceName, displayName, hostName, caller, owner, userId,
13511352
serviceOffering, dataDiskOfferingMap,
13521353
nicNetworkMap, nicIpAddressMap,
1353-
details, cmd.getMigrateAllowed(), managedVms, forced);
1354+
details, cmd.getMigrateAllowed(), managedVms, forced, guestOsId);
13541355
}
13551356
}
13561357

@@ -1497,7 +1498,7 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl
14971498
String hostName, Account caller, Account owner, long userId,
14981499
ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
14991500
Map<String, Long> nicNetworkMap, Map<String, Network.IpAddresses> nicIpAddressMap,
1500-
Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced) throws ResourceAllocationException {
1501+
Map<String, String> details, Boolean migrateAllowed, List<String> managedVms, boolean forced, Long guestOsId) throws ResourceAllocationException {
15011502
UserVm userVm = null;
15021503
for (HostVO host : hosts) {
15031504
HashMap<String, UnmanagedInstanceTO> unmanagedInstances = getUnmanagedInstancesForHost(host, instanceName, managedVms);
@@ -1548,7 +1549,7 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl
15481549
userVm = importVirtualMachineInternal(unmanagedInstance, instanceName, zone, cluster, host,
15491550
template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId,
15501551
serviceOffering, dataDiskOfferingMap,
1551-
nicNetworkMap, nicIpAddressMap, null,
1552+
nicNetworkMap, nicIpAddressMap, guestOsId,
15521553
details, migrateAllowed, forced, true);
15531554
} finally {
15541555
ReservationHelper.closeAll(reservations);
@@ -2611,6 +2612,9 @@ private UserVmResponse importKvmInstance(ImportVmCmd cmd) {
26112612
Long hostId = cmd.getHostId();
26122613
Long poolId = cmd.getStoragePoolId();
26132614
Long networkId = cmd.getNetworkId();
2615+
Long guestOsId = cmd.getGuestOsId();
2616+
String macAddress = cmd.getMacAddress();
2617+
String ipAddress = cmd.getIpAddress();
26142618

26152619
UnmanagedInstanceTO unmanagedInstanceTO = null;
26162620
if (ImportSource.EXTERNAL == importSource) {
@@ -2679,12 +2683,12 @@ private UserVmResponse importKvmInstance(ImportVmCmd cmd) {
26792683
userVm = importExternalKvmVirtualMachine(unmanagedInstanceTO, instanceName, zone,
26802684
template, displayName, hostName, caller, owner, userId,
26812685
serviceOffering, dataDiskOfferingMap,
2682-
nicNetworkMap, nicIpAddressMap, remoteUrl, username, password, tmpPath, details);
2686+
nicNetworkMap, nicIpAddressMap, guestOsId, remoteUrl, username, password, tmpPath, details);
26832687
} else if (ImportSource.SHARED == importSource || ImportSource.LOCAL == importSource) {
26842688
try {
26852689
userVm = importKvmVirtualMachineFromDisk(importSource, instanceName, zone,
26862690
template, displayName, hostName, caller, owner, userId,
2687-
serviceOffering, dataDiskOfferingMap, networkId, hostId, poolId, diskPath,
2691+
serviceOffering, dataDiskOfferingMap, networkId, macAddress, ipAddress, guestOsId, hostId, poolId, diskPath,
26882692
details);
26892693
} catch (InsufficientCapacityException e) {
26902694
throw new RuntimeException(e);
@@ -2711,7 +2715,7 @@ private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanag
27112715
final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
27122716
final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap,
27132717
final Map<String, Long> nicNetworkMap, final Map<String, Network.IpAddresses> callerNicIpAddressMap,
2714-
final String remoteUrl, String username, String password, String tmpPath, final Map<String, String> details) throws ResourceAllocationException {
2718+
final Long guestOsId, final String remoteUrl, String username, String password, String tmpPath, final Map<String, String> details) throws ResourceAllocationException {
27152719
UserVm userVm = null;
27162720

27172721
Map<String, String> allDetails = new HashMap<>(details);
@@ -2747,7 +2751,7 @@ private UserVm importExternalKvmVirtualMachine(final UnmanagedInstanceTO unmanag
27472751
try {
27482752
userVm = userVmManager.importVM(zone, null, template, null, displayName, owner,
27492753
null, caller, true, null, owner.getAccountId(), userId,
2750-
serviceOffering, null, null, hostName,
2754+
serviceOffering, null, guestOsId, hostName,
27512755
Hypervisor.HypervisorType.KVM, allDetails, powerState, null);
27522756
} catch (InsufficientCapacityException ice) {
27532757
logger.error(String.format("Failed to import vm name: %s", instanceName), ice);
@@ -2848,6 +2852,7 @@ protected void checkVolumeResourceLimitsForExternalKvmVmImport(Account owner, Un
28482852
private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource, final String instanceName, final DataCenter zone,
28492853
final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId,
28502854
final ServiceOfferingVO serviceOffering, final Map<String, Long> dataDiskOfferingMap, final Long networkId,
2855+
final String requestedMacAddress, final String requestedIpAddress, final Long guestOsId,
28512856
final Long hostId, final Long poolId, final String diskPath, final Map<String, String> details) throws InsufficientCapacityException, ResourceAllocationException {
28522857

28532858
UserVm userVm = null;
@@ -2878,9 +2883,15 @@ private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource,
28782883
}
28792884
}
28802885

2881-
String macAddress = networkModel.getNextAvailableMacAddressInNetwork(networkId);
2886+
String macAddress = StringUtils.defaultIfBlank(requestedMacAddress, networkModel.getNextAvailableMacAddressInNetwork(networkId));
2887+
if (!NetUtils.isValidMac(macAddress)) {
2888+
throw new InvalidParameterValueException("MAC address is invalid: " + macAddress);
2889+
}
28822890

2883-
String ipAddress = network.getGuestType() != Network.GuestType.L2 ? "auto" : null;
2891+
String ipAddress = StringUtils.defaultIfBlank(requestedIpAddress, network.getGuestType() != Network.GuestType.L2 ? "auto" : null);
2892+
if (StringUtils.isNotBlank(ipAddress) && !"auto".equals(ipAddress) && !NetUtils.isValidIp4(ipAddress)) {
2893+
throw new InvalidParameterValueException("IP address is invalid: " + ipAddress);
2894+
}
28842895

28852896
Network.IpAddresses requestedIpPair = new Network.IpAddresses(ipAddress, null, macAddress);
28862897

@@ -2903,7 +2914,7 @@ private UserVm importKvmVirtualMachineFromDisk(final ImportSource importSource,
29032914
checkVmResourceLimitsForExternalKvmVmImport(owner, serviceOffering, (VMTemplateVO) template, details, reservations);
29042915
userVm = userVmManager.importVM(zone, null, template, null, displayName, owner,
29052916
null, caller, true, null, owner.getAccountId(), userId,
2906-
serviceOffering, null, null, hostName,
2917+
serviceOffering, null, guestOsId, hostName,
29072918
Hypervisor.HypervisorType.KVM, allDetails, powerState, networkNicMap);
29082919

29092920
if (userVm == null) {

0 commit comments

Comments
 (0)