Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d3668e2
pre-allocate a second empty cdrom slot at boot (hardcoded)
Damans227 May 4, 2026
90b415f
drive cdrom slot count via vm.cdrom.max.count ConfigKey
Damans227 May 4, 2026
0516f82
add vm_iso_map table + VO/DAO
Damans227 May 4, 2026
945f2bd
persist multi-ISO state via vm_iso_map
Damans227 May 4, 2026
9570369
carry target cdrom slot through AttachCommand to KVM agent
Damans227 May 4, 2026
5fd487a
enforce per-VM cdrom cap, clamp to hypervisor max
Damans227 May 4, 2026
3a13ac7
make detachIso accepts an ISO id
Damans227 May 4, 2026
b05b9f9
expose attached ISOs as isos[] in listVirtualMachines response
Damans227 May 4, 2026
6d2ae5a
extract CDROM_PRIMARY_DEVICE_SEQ constant
Damans227 May 4, 2026
a44b4fb
unit tests for cdrom slot allocation logic
Damans227 May 4, 2026
a6afd26
implement multi-ISO attachment and detachment for VMs with enhanced v…
Damans227 May 4, 2026
71fe082
implement multi-ISO display in InstanceTab with computed property for…
Damans227 May 4, 2026
2ff3ce6
add warning alert for max CDROM selections and enhance global capacit…
Damans227 May 4, 2026
e394f1c
enhance ISO attachment validation to handle multiple ISOs and prevent…
Damans227 May 4, 2026
e7185ab
refactor ISO attachment logic for detachment and validation
Damans227 May 4, 2026
1e5bd63
add unit tests for ISO detachment resolution and validation logic
Damans227 May 4, 2026
79e8edf
add mock for VmIsoMapDao in UserVmJoinDaoImplTest and set lenient beh…
Damans227 May 4, 2026
bac8122
refactor ISO attachment logic and enhance UI for multi-CDROM management
Damans227 May 4, 2026
0570cf2
refactor ISO attachment methods to use VM ID and improve parameter ha…
Damans227 May 4, 2026
61396a1
remove unnecessary mock for VM ISO mapping in TemplateManagerImplTest
Damans227 May 4, 2026
fdaffb5
add 'since' attribute to ISO detach command parameter description
Damans227 May 5, 2026
6634da7
scope vm.cdrom.max.count to cluster
Damans227 May 5, 2026
082896e
add support for configurable CD-ROM count per VM and improve handling…
Damans227 May 5, 2026
ce530d2
add HostDetailsDao mock to UserVmJoinDaoImplTest
Damans227 May 5, 2026
7717002
fix: handle null poolId when loading attached ISO slots in prepareIso…
Damans227 May 5, 2026
2932522
implement listByIsoId method in VmIsoMapDao and update TemplateManage…
Damans227 May 5, 2026
22fae71
improve logging messages for ISO deletion checks
Damans227 May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/host/Host.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static String[] toStrings(Host.Type... types) {
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
String HOST_SSH_PORT = "host.ssh.port";
String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count";

int DEFAULT_SSH_PORT = 22;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserVmResponse;

import com.cloud.event.EventTypes;
Expand All @@ -51,6 +52,10 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd {
description = "If true, ejects the ISO before detaching on VMware. Default: false", since = "4.15.1")
protected Boolean forced;

@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class,
description = "The ID of the ISO to detach. Required when the Instance has more than one ISO attached.", since = "4.23.0")
protected Long id;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -104,7 +109,7 @@ public ApiCommandResourceType getApiResourceType() {

@Override
public void execute() {
boolean result = _templateService.detachIso(virtualMachineId, null, isForced());
boolean result = _templateService.detachIso(virtualMachineId, id, isForced());
if (result) {
UserVm userVm = _entityMgr.findById(UserVm.class, virtualMachineId);
UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", userVm).get(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 org.apache.cloudstack.api.BaseResponse;

import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;

public class AttachedIsoResponse extends BaseResponse {

@SerializedName("id")
@Param(description = "The ID of the attached ISO")
private String id;

@SerializedName("name")
@Param(description = "The name of the attached ISO")
private String name;

@SerializedName("displaytext")
@Param(description = "The display text of the attached ISO")
private String displayText;

@SerializedName("deviceseq")
@Param(description = "The cdrom slot that holds this ISO (3=hdc, 4=hdd, ...)")
private Integer deviceSeq;

public AttachedIsoResponse() {
}

public AttachedIsoResponse(String id, String name, String displayText, Integer deviceSeq) {
this.id = id;
this.name = name;
this.displayText = displayText;
this.deviceSeq = deviceSeq;
}

public String getId() {
return id;
}

public String getName() {
return name;
}

public String getDisplayText() {
return displayText;
}

public Integer getDeviceSeq() {
return deviceSeq;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "An alternate display text of the ISO attached to the Instance")
private String isoDisplayText;

@SerializedName("isos")
@Param(description = "All ISOs attached to the Instance, keyed by cdrom slot. The first entry mirrors isoid/isoname for back-compat.", responseObject = AttachedIsoResponse.class, since = "4.23.0")
private List<AttachedIsoResponse> isos;

@SerializedName("cdrommaxcount")
@Param(description = "Maximum number of CD-ROM drives this Instance may have, after applying the cluster-scoped vm.cdrom.max.count and the hypervisor's own cap.", since = "4.23.0")
private Integer cdromMaxCount;

@SerializedName(ApiConstants.SERVICE_OFFERING_ID)
@Param(description = "The ID of the service offering of the Instance")
private String serviceOfferingId;
Expand Down Expand Up @@ -871,6 +879,22 @@ public void setIsoId(String isoId) {
this.isoId = isoId;
}

public void setIsos(List<AttachedIsoResponse> isos) {
this.isos = isos;
}

public List<AttachedIsoResponse> getIsos() {
return isos;
}

public void setCdromMaxCount(Integer cdromMaxCount) {
this.cdromMaxCount = cdromMaxCount;
}

public Integer getCdromMaxCount() {
return cdromMaxCount;
}

public void setIsoName(String isoName) {
this.isoName = isoName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ public interface TemplateManager {
true,
ConfigKey.Scope.Global);

ConfigKey<Integer> VmCdromMaxCount = new ConfigKey<Integer>("Advanced",
Integer.class,
"vm.cdrom.max.count", "1",
"Maximum number of CD-ROM drives per VM.",
true,
ConfigKey.Scope.Cluster);

// KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE).
// user_vm.iso_id has always pointed at this slot; additional cdroms live in vm_iso_map.
int CDROM_PRIMARY_DEVICE_SEQ = 3;

// Fallback per-VM cdrom cap when the placement host hasn't advertised host.cdrom.max.count
// (older agent, never-deployed VM, etc.).
int DEFAULT_CDROM_MAX_PER_VM = 1;

static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
static final String XS_TOOLS_ISO = "xs-tools.iso";

Expand Down
83 changes: 83 additions & 0 deletions engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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.vm;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.apache.cloudstack.api.InternalIdentity;

@Entity
@Table(name = "vm_iso_map")
public class VmIsoMapVO implements InternalIdentity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "vm_id")
private long vmId;

@Column(name = "iso_id")
private long isoId;

@Column(name = "device_seq")
private int deviceSeq;

@Column(name = "created")
@Temporal(TemporalType.TIMESTAMP)
private Date created;

public VmIsoMapVO() {
}

public VmIsoMapVO(long vmId, long isoId, int deviceSeq) {
this.vmId = vmId;
this.isoId = isoId;
this.deviceSeq = deviceSeq;
this.created = new Date();
}

@Override
public long getId() {
return id;
}

public long getVmId() {
return vmId;
}

public long getIsoId() {
return isoId;
}

public int getDeviceSeq() {
return deviceSeq;
}

public Date getCreated() {
return created;
}
}
34 changes: 34 additions & 0 deletions engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.vm.dao;

import java.util.List;

import com.cloud.utils.db.GenericDao;
import com.cloud.vm.VmIsoMapVO;

public interface VmIsoMapDao extends GenericDao<VmIsoMapVO, Long> {
List<VmIsoMapVO> listByVmId(long vmId);

List<VmIsoMapVO> listByIsoId(long isoId);

VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq);

VmIsoMapVO findByVmIdIsoId(long vmId, long isoId);

int removeByVmId(long vmId);
}
92 changes: 92 additions & 0 deletions engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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.vm.dao;

import java.util.List;

import org.springframework.stereotype.Component;

import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.vm.VmIsoMapVO;

@Component
public class VmIsoMapDaoImpl extends GenericDaoBase<VmIsoMapVO, Long> implements VmIsoMapDao {

private SearchBuilder<VmIsoMapVO> ListByVmId;
private SearchBuilder<VmIsoMapVO> ListByIsoId;
private SearchBuilder<VmIsoMapVO> ByVmIdDeviceSeq;
private SearchBuilder<VmIsoMapVO> ByVmIdIsoId;

protected VmIsoMapDaoImpl() {
ListByVmId = createSearchBuilder();
ListByVmId.and("vmId", ListByVmId.entity().getVmId(), SearchCriteria.Op.EQ);
ListByVmId.done();

ListByIsoId = createSearchBuilder();
ListByIsoId.and("isoId", ListByIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
ListByIsoId.done();

ByVmIdDeviceSeq = createSearchBuilder();
ByVmIdDeviceSeq.and("vmId", ByVmIdDeviceSeq.entity().getVmId(), SearchCriteria.Op.EQ);
ByVmIdDeviceSeq.and("deviceSeq", ByVmIdDeviceSeq.entity().getDeviceSeq(), SearchCriteria.Op.EQ);
ByVmIdDeviceSeq.done();

ByVmIdIsoId = createSearchBuilder();
ByVmIdIsoId.and("vmId", ByVmIdIsoId.entity().getVmId(), SearchCriteria.Op.EQ);
ByVmIdIsoId.and("isoId", ByVmIdIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
ByVmIdIsoId.done();
}

@Override
public List<VmIsoMapVO> listByVmId(long vmId) {
SearchCriteria<VmIsoMapVO> sc = ListByVmId.create();
sc.setParameters("vmId", vmId);
return listBy(sc);
}

@Override
public List<VmIsoMapVO> listByIsoId(long isoId) {
SearchCriteria<VmIsoMapVO> sc = ListByIsoId.create();
sc.setParameters("isoId", isoId);
return listBy(sc);
}

@Override
public VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq) {
SearchCriteria<VmIsoMapVO> sc = ByVmIdDeviceSeq.create();
sc.setParameters("vmId", vmId);
sc.setParameters("deviceSeq", deviceSeq);
return findOneBy(sc);
}

@Override
public VmIsoMapVO findByVmIdIsoId(long vmId, long isoId) {
SearchCriteria<VmIsoMapVO> sc = ByVmIdIsoId.create();
sc.setParameters("vmId", vmId);
sc.setParameters("isoId", isoId);
return findOneBy(sc);
}

@Override
public int removeByVmId(long vmId) {
SearchCriteria<VmIsoMapVO> sc = ListByVmId.create();
sc.setParameters("vmId", vmId);
return remove(sc);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<bean id="instanceGroupJoinDaoImpl" class="com.cloud.api.query.dao.InstanceGroupJoinDaoImpl" />
<bean id="managementServerJoinDaoImpl" class="com.cloud.api.query.dao.ManagementServerJoinDaoImpl" />
<bean id="instanceGroupVMMapDaoImpl" class="com.cloud.vm.dao.InstanceGroupVMMapDaoImpl" />
<bean id="vmIsoMapDaoImpl" class="com.cloud.vm.dao.VmIsoMapDaoImpl" />
<bean id="itWorkDaoImpl" class="com.cloud.vm.ItWorkDaoImpl" />
<bean id="lBHealthCheckPolicyDaoImpl" class="com.cloud.network.dao.LBHealthCheckPolicyDaoImpl" />
<bean id="lBStickinessPolicyDaoImpl" class="com.cloud.network.dao.LBStickinessPolicyDaoImpl" />
Expand Down
Loading
Loading