NAS BnR: Create Instance from Backup issues (#11754)

* add createCrossZoneInstnaceEnabled to BackupOfferingResponse

* show use IP Address from Backup button when orignal instance is expunged

* Fix NPE in takeBackup if the  vm template is deleted.

* Add since to Cross zone instance creation in BackupOfferingResponse.java

Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>

* Store and show Guest os type in the backup metadata

* show warning in create instance from backup form if guest os type is different

* show warning in create instance from backup form if guest os type is different

* backupvmexpunged -> isbackupvmexpunged

* review comments

* fix npe

* improve err msg

* err msg

---------

Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
This commit is contained in:
Abhisar Sinha 2025-10-14 14:51:57 +05:30 committed by GitHub
parent c0a4392b05
commit 046014b4c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 133 additions and 52 deletions

View File

@ -64,6 +64,7 @@ public class ApiConstants {
public static final String BACKUP_STORAGE_LIMIT = "backupstoragelimit"; public static final String BACKUP_STORAGE_LIMIT = "backupstoragelimit";
public static final String BACKUP_STORAGE_TOTAL = "backupstoragetotal"; public static final String BACKUP_STORAGE_TOTAL = "backupstoragetotal";
public static final String BACKUP_VM_OFFERING_REMOVED = "vmbackupofferingremoved"; public static final String BACKUP_VM_OFFERING_REMOVED = "vmbackupofferingremoved";
public static final String IS_BACKUP_VM_EXPUNGED = "isbackupvmexpunged";
public static final String BACKUP_TOTAL = "backuptotal"; public static final String BACKUP_TOTAL = "backuptotal";
public static final String BASE64_IMAGE = "base64image"; public static final String BASE64_IMAGE = "base64image";
public static final String BGP_PEERS = "bgppeers"; public static final String BGP_PEERS = "bgppeers";

View File

@ -61,6 +61,10 @@ public class BackupOfferingResponse extends BaseResponse {
@Param(description = "zone name") @Param(description = "zone name")
private String zoneName; private String zoneName;
@SerializedName(ApiConstants.CROSS_ZONE_INSTANCE_CREATION)
@Param(description = "the backups with this offering can be used to create Instances on all Zones", since = "4.22.0")
private Boolean crossZoneInstanceCreation;
@SerializedName(ApiConstants.CREATED) @SerializedName(ApiConstants.CREATED)
@Param(description = "the date this backup offering was created") @Param(description = "the date this backup offering was created")
private Date created; private Date created;
@ -97,6 +101,10 @@ public class BackupOfferingResponse extends BaseResponse {
this.zoneName = zoneName; this.zoneName = zoneName;
} }
public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) {
this.crossZoneInstanceCreation = crossZoneInstanceCreation;
}
public void setCreated(Date created) { public void setCreated(Date created) {
this.created = created; this.created = created;
} }

View File

@ -123,6 +123,10 @@ public class BackupResponse extends BaseResponse {
@Param(description = "The backup offering corresponding to this backup was removed from the VM", since = "4.21.0") @Param(description = "The backup offering corresponding to this backup was removed from the VM", since = "4.21.0")
private Boolean vmOfferingRemoved; private Boolean vmOfferingRemoved;
@SerializedName(ApiConstants.IS_BACKUP_VM_EXPUNGED)
@Param(description = "Indicates whether the VM from which the backup was taken is expunged or not", since = "4.22.0")
private Boolean isVmExpunged;
public String getId() { public String getId() {
return id; return id;
} }
@ -306,4 +310,8 @@ public class BackupResponse extends BaseResponse {
public void setVmOfferingRemoved(Boolean vmOfferingRemoved) { public void setVmOfferingRemoved(Boolean vmOfferingRemoved) {
this.vmOfferingRemoved = vmOfferingRemoved; this.vmOfferingRemoved = vmOfferingRemoved;
} }
public void setVmExpunged(Boolean isVmExpunged) {
this.isVmExpunged = isVmExpunged;
}
} }

View File

@ -24,7 +24,7 @@ import org.apache.cloudstack.backup.BackupOfferingVO;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
public interface BackupOfferingDao extends GenericDao<BackupOfferingVO, Long> { public interface BackupOfferingDao extends GenericDao<BackupOfferingVO, Long> {
BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy); BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy, Boolean crossZoneInstanceCreation);
BackupOffering findByExternalId(String externalId, Long zoneId); BackupOffering findByExternalId(String externalId, Long zoneId);
BackupOffering findByName(String name, Long zoneId); BackupOffering findByName(String name, Long zoneId);
} }

View File

@ -50,7 +50,7 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
} }
@Override @Override
public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) { public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, Boolean crossZoneInstanceCreation) {
DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); DataCenterVO zone = dataCenterDao.findById(offering.getZoneId());
BackupOfferingResponse response = new BackupOfferingResponse(); BackupOfferingResponse response = new BackupOfferingResponse();
@ -64,6 +64,9 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
response.setZoneId(zone.getUuid()); response.setZoneId(zone.getUuid());
response.setZoneName(zone.getName()); response.setZoneName(zone.getName());
} }
if (crossZoneInstanceCreation) {
response.setCrossZoneInstanceCreation(true);
}
response.setCreated(offering.getCreated()); response.setCreated(offering.getCreated());
response.setObjectName("backupoffering"); response.setObjectName("backupoffering");
return response; return response;

View File

@ -75,9 +75,11 @@ import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.VpcOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.BackupRepository;
import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupRepositoryDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -493,6 +495,7 @@ public class ApiDBUtils {
static BackupDao s_backupDao; static BackupDao s_backupDao;
static BackupScheduleDao s_backupScheduleDao; static BackupScheduleDao s_backupScheduleDao;
static BackupOfferingDao s_backupOfferingDao; static BackupOfferingDao s_backupOfferingDao;
static BackupRepositoryDao s_backupRepositoryDao;
static NicDao s_nicDao; static NicDao s_nicDao;
static ResourceManagerUtil s_resourceManagerUtil; static ResourceManagerUtil s_resourceManagerUtil;
static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao; static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
@ -751,6 +754,8 @@ public class ApiDBUtils {
@Inject @Inject
private BackupOfferingDao backupOfferingDao; private BackupOfferingDao backupOfferingDao;
@Inject @Inject
private BackupRepositoryDao backupRepositoryDao;
@Inject
private BackupScheduleDao backupScheduleDao; private BackupScheduleDao backupScheduleDao;
@Inject @Inject
private NicDao nicDao; private NicDao nicDao;
@ -899,6 +904,7 @@ public class ApiDBUtils {
s_backupDao = backupDao; s_backupDao = backupDao;
s_backupScheduleDao = backupScheduleDao; s_backupScheduleDao = backupScheduleDao;
s_backupOfferingDao = backupOfferingDao; s_backupOfferingDao = backupOfferingDao;
s_backupRepositoryDao = backupRepositoryDao;
s_resourceIconDao = resourceIconDao; s_resourceIconDao = resourceIconDao;
s_resourceManagerUtil = resourceManagerUtil; s_resourceManagerUtil = resourceManagerUtil;
s_objectStoreDao = objectStoreDao; s_objectStoreDao = objectStoreDao;
@ -2297,8 +2303,10 @@ public class ApiDBUtils {
return s_backupScheduleDao.newBackupScheduleResponse(schedule); return s_backupScheduleDao.newBackupScheduleResponse(schedule);
} }
public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) { public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) {
return s_backupOfferingDao.newBackupOfferingResponse(policy); BackupRepository repository = s_backupRepositoryDao.findByUuid(offering.getExternalId());
Boolean crossZoneInstanceCreationEnabled = repository != null ? Boolean.TRUE.equals(repository.crossZoneInstanceCreationEnabled()) : false;
return s_backupOfferingDao.newBackupOfferingResponse(offering, crossZoneInstanceCreationEnabled);
} }
public static NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId) { public static NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId) {

View File

@ -5040,8 +5040,8 @@ public class ApiResponseHelper implements ResponseGenerator {
} }
@Override @Override
public BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy) { public BackupOfferingResponse createBackupOfferingResponse(BackupOffering offering) {
return ApiDBUtils.newBackupOfferingResponse(policy); return ApiDBUtils.newBackupOfferingResponse(offering);
} }
public ManagementServerResponse createManagementResponse(ManagementServerHost mgmt) { public ManagementServerResponse createManagementResponse(ManagementServerHost mgmt) {

View File

@ -9606,11 +9606,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} else { } else {
String serviceOfferingUuid = backup.getDetail(ApiConstants.SERVICE_OFFERING_ID); String serviceOfferingUuid = backup.getDetail(ApiConstants.SERVICE_OFFERING_ID);
if (serviceOfferingUuid == null) { if (serviceOfferingUuid == null) {
throw new CloudRuntimeException("Backup doesn't contain service offering uuid. Please specify a valid service offering id while creating the instance"); throw new CloudRuntimeException("Backup doesn't contain a Service Offering UUID. Please specify a valid Service Offering while creating the Instance");
} }
serviceOffering = serviceOfferingDao.findByUuid(serviceOfferingUuid); serviceOffering = serviceOfferingDao.findByUuid(serviceOfferingUuid);
if (serviceOffering == null) { if (serviceOffering == null) {
throw new CloudRuntimeException("Unable to find service offering with the uuid stored in backup. Please specify a valid service offering id while creating instance"); throw new CloudRuntimeException("Unable to find Service Offering with the UUID stored in the Backup. Please specify a valid Service Offering while creating the Instance");
} }
} }
verifyServiceOffering(cmd, serviceOffering); verifyServiceOffering(cmd, serviceOffering);
@ -9625,11 +9625,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} else { } else {
String templateUuid = backup.getDetail(ApiConstants.TEMPLATE_ID); String templateUuid = backup.getDetail(ApiConstants.TEMPLATE_ID);
if (templateUuid == null) { if (templateUuid == null) {
throw new CloudRuntimeException("Backup doesn't contain Template uuid. Please specify a valid Template/ISO while creating the instance"); throw new CloudRuntimeException("Backup doesn't contain a Template UUID. Please specify a valid Template/ISO while creating the Instance");
} }
template = _templateDao.findByUuid(templateUuid); template = _templateDao.findByUuid(templateUuid);
if (template == null) { if (template == null) {
throw new CloudRuntimeException("Unable to find template associated with the backup. Please specify a valid Template/ISO while creating instance"); throw new CloudRuntimeException("Unable to find Template with the UUID stored in the Backup. Please specify a valid Template/ISO while creating the Instance");
} }
} }
verifyTemplate(cmd, template, serviceOffering.getId()); verifyTemplate(cmd, template, serviceOffering.getId());

View File

@ -122,12 +122,14 @@ import com.cloud.projects.Project;
import com.cloud.serializer.GsonHelper; import com.cloud.serializer.GsonHelper;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.storage.Volume; import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO; import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDao;
import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate;
@ -232,6 +234,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
private ResourceLimitService resourceLimitMgr; private ResourceLimitService resourceLimitMgr;
@Inject @Inject
private AlertManager alertManager; private AlertManager alertManager;
@Inject
private GuestOSDao _guestOSDao;
private AsyncJobDispatcher asyncJobDispatcher; private AsyncJobDispatcher asyncJobDispatcher;
private Timer backupTimer; private Timer backupTimer;
@ -379,7 +383,15 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
ServiceOffering serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId()); ServiceOffering serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid()); details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
VirtualMachineTemplate template = vmTemplateDao.findById(vm.getTemplateId()); VirtualMachineTemplate template = vmTemplateDao.findById(vm.getTemplateId());
details.put(ApiConstants.TEMPLATE_ID, template.getUuid()); if (template != null) {
long guestOSId = template.getGuestOSId();
details.put(ApiConstants.TEMPLATE_ID, template.getUuid());
GuestOSVO guestOS = _guestOSDao.findById(guestOSId);
if (guestOS != null) {
details.put(ApiConstants.OS_TYPE_ID, guestOS.getUuid());
details.put(ApiConstants.OS_NAME, guestOS.getDisplayName());
}
}
List<VMInstanceDetailVO> vmDetails = vmInstanceDetailsDao.listDetails(vm.getId()); List<VMInstanceDetailVO> vmDetails = vmInstanceDetailsDao.listDetails(vm.getId());
HashMap<String, String> settings = new HashMap<>(); HashMap<String, String> settings = new HashMap<>();
@ -2143,7 +2155,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
if (details.containsKey(ApiConstants.TEMPLATE_ID)) { if (details.containsKey(ApiConstants.TEMPLATE_ID)) {
VirtualMachineTemplate template = vmTemplateDao.findByUuid(details.get(ApiConstants.TEMPLATE_ID)); VirtualMachineTemplate template = vmTemplateDao.findByUuid(details.get(ApiConstants.TEMPLATE_ID));
if (template != null) { if (template != null) {
details.put(ApiConstants.TEMPLATE_ID, template.getUuid());
details.put(ApiConstants.TEMPLATE_NAME, template.getName()); details.put(ApiConstants.TEMPLATE_NAME, template.getName());
details.put(ApiConstants.IS_ISO, String.valueOf(template.getFormat().equals(Storage.ImageFormat.ISO))); details.put(ApiConstants.IS_ISO, String.valueOf(template.getFormat().equals(Storage.ImageFormat.ISO)));
} }
@ -2151,7 +2162,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
if (details.containsKey(ApiConstants.SERVICE_OFFERING_ID)) { if (details.containsKey(ApiConstants.SERVICE_OFFERING_ID)) {
ServiceOffering serviceOffering = serviceOfferingDao.findByUuid(details.get(ApiConstants.SERVICE_OFFERING_ID)); ServiceOffering serviceOffering = serviceOfferingDao.findByUuid(details.get(ApiConstants.SERVICE_OFFERING_ID));
if (serviceOffering != null) { if (serviceOffering != null) {
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
details.put(ApiConstants.SERVICE_OFFERING_NAME, serviceOffering.getName()); details.put(ApiConstants.SERVICE_OFFERING_NAME, serviceOffering.getName());
} }
} }
@ -2186,10 +2196,15 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
response.setId(backup.getUuid()); response.setId(backup.getUuid());
response.setName(backup.getName()); response.setName(backup.getName());
response.setDescription(backup.getDescription()); response.setDescription(backup.getDescription());
response.setVmName(vm.getHostName()); if (vm != null) {
response.setVmId(vm.getUuid()); response.setVmName(vm.getHostName());
if (vm.getBackupOfferingId() == null || vm.getBackupOfferingId() != backup.getBackupOfferingId()) { response.setVmId(vm.getUuid());
response.setVmOfferingRemoved(true); if (vm.getBackupOfferingId() == null || vm.getBackupOfferingId() != backup.getBackupOfferingId()) {
response.setVmOfferingRemoved(true);
}
}
if (vm == null || VirtualMachine.State.Expunging.equals(vm.getState())) {
response.setVmExpunged(true);
} }
response.setExternalId(backup.getExternalId()); response.setExternalId(backup.getExternalId());
response.setType(backup.getType()); response.setType(backup.getType());
@ -2205,9 +2220,11 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
} }
} }
// ACS 4.20: For backups taken prior this release the backup.backed_volumes column would be empty hence use vm_instance.backup_volumes // ACS 4.20: For backups taken prior this release the backup.backed_volumes column would be empty hence use vm_instance.backup_volumes
String backedUpVolumes; String backedUpVolumes = "";
if (Objects.isNull(backup.getBackedUpVolumes())) { if (Objects.isNull(backup.getBackedUpVolumes())) {
backedUpVolumes = new Gson().toJson(vm.getBackupVolumeList().toArray(), Backup.VolumeInfo[].class); if (vm != null) {
backedUpVolumes = new Gson().toJson(vm.getBackupVolumeList().toArray(), Backup.VolumeInfo[].class);
}
} else { } else {
backedUpVolumes = new Gson().toJson(backup.getBackedUpVolumes().toArray(), Backup.VolumeInfo[].class); backedUpVolumes = new Gson().toJson(backup.getBackedUpVolumes().toArray(), Backup.VolumeInfo[].class);
} }
@ -2223,7 +2240,9 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
if (Boolean.TRUE.equals(listVmDetails)) { if (Boolean.TRUE.equals(listVmDetails)) {
Map<String, String> vmDetails = new HashMap<>(); Map<String, String> vmDetails = new HashMap<>();
vmDetails.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString()); if (vm != null) {
vmDetails.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString());
}
Map<String, String> details = getDetailsFromBackupDetails(backup.getId()); Map<String, String> details = getDetailsFromBackupDetails(backup.getId());
vmDetails.putAll(details); vmDetails.putAll(details);
response.setVmDetails(vmDetails); response.setVmDetails(vmDetails);

View File

@ -49,6 +49,7 @@ import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO; import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account; import com.cloud.user.Account;
@ -237,6 +238,9 @@ public class BackupManagerTest {
@Mock @Mock
DomainDao domainDao; DomainDao domainDao;
@Mock
private GuestOSDao _guestOSDao;
private Gson gson; private Gson gson;
private String[] hostPossibleValues = {"127.0.0.1", "hostname"}; private String[] hostPossibleValues = {"127.0.0.1", "hostname"};
@ -1572,14 +1576,12 @@ public class BackupManagerTest {
VMTemplateVO template = mock(VMTemplateVO.class); VMTemplateVO template = mock(VMTemplateVO.class);
when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
when(template.getUuid()).thenReturn(templateUuid);
when(template.getName()).thenReturn("template1"); when(template.getName()).thenReturn("template1");
when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template); when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template);
Map<String, String> details = new HashMap<>(); Map<String, String> details = new HashMap<>();
details.put(ApiConstants.TEMPLATE_ID, templateUuid); details.put(ApiConstants.TEMPLATE_ID, templateUuid);
ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class);
when(serviceOffering.getUuid()).thenReturn(serviceOfferingUuid);
when(serviceOffering.getName()).thenReturn("service-offering1"); when(serviceOffering.getName()).thenReturn("service-offering1");
when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering);
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid);

View File

@ -2887,6 +2887,8 @@
"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot", "message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot",
"message.action.create.instance.from.backup": "Please confirm that you want to create a new Instance from the given Backup.<br>Click on configure to edit the parameters for the new Instance before creation.", "message.action.create.instance.from.backup": "Please confirm that you want to create a new Instance from the given Backup.<br>Click on configure to edit the parameters for the new Instance before creation.",
"message.create.instance.from.backup.different.zone": "Creating Instance from Backup on a different Zone. Please ensure that the backup repository is accessible in the selected Zone.", "message.create.instance.from.backup.different.zone": "Creating Instance from Backup on a different Zone. Please ensure that the backup repository is accessible in the selected Zone.",
"message.template.ostype.different.from.backup": "Selected Template has a different OS type than the Backup. Please proceed with caution.",
"message.iso.ostype.different.from.backup": "Selected ISO has a different OS type than the Backup. Please proceed with caution.",
"message.action.delete.asnrange": "Please confirm the AS range that you want to delete", "message.action.delete.asnrange": "Please confirm the AS range that you want to delete",
"message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscaling group.", "message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscaling group.",
"message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?", "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?",

View File

@ -42,6 +42,11 @@
{{ getTemplateDisplayName() }} {{ getTemplateDisplayName() }}
</router-link> </router-link>
</div> </div>
<div v-else-if="item === 'ostypeid'">
<router-link :to="{ path: '/guestos' + '/' + backupMetadata[item] }">
{{ backupMetadata.osname }}
</router-link>
</div>
<div v-else-if="item === 'serviceofferingid'"> <div v-else-if="item === 'serviceofferingid'">
<router-link :to="{ path: '/computeoffering/' + backupMetadata[item] }"> <router-link :to="{ path: '/computeoffering/' + backupMetadata[item] }">
{{ getServiceOfferingDisplayName() }} {{ getServiceOfferingDisplayName() }}
@ -84,15 +89,14 @@ export default {
if (!this.backupMetadata || Object.keys(this.backupMetadata).length === 0) { if (!this.backupMetadata || Object.keys(this.backupMetadata).length === 0) {
return [] return []
} }
const fieldOrder = [] const fieldOrder = [
fieldOrder.push('templateid') 'templateid',
if (this.backupMetadata.isiso === 'true') { 'ostypeid',
fieldOrder.push('hypervisor') 'hypervisor',
} 'serviceofferingid',
fieldOrder.push('serviceofferingid') 'nics',
fieldOrder.push('nics') 'vmsettings'
fieldOrder.push('vmsettings') ]
return fieldOrder.filter(field => this.backupMetadata[field] !== undefined) return fieldOrder.filter(field => this.backupMetadata[field] !== undefined)
}, },
getNicEntities () { getNicEntities () {

View File

@ -168,6 +168,23 @@
:tabList="tabList" :tabList="tabList"
:activeTabKey="tabKey" :activeTabKey="tabKey"
@tabChange="key => onTabChange(key, 'tabKey')"> @tabChange="key => onTabChange(key, 'tabKey')">
<a-alert
v-if="showOsTypeWarning && selectedTemplateIso"
style="margin-bottom: 10px">
<template #message>
<div>
{{ selectedTemplateIso.message }}<br>
{{ selectedTemplateIso.label }} :
<router-link :to="{ path: '/guestos/' + selectedTemplateIso.item.ostypeid }">
{{ selectedTemplateIso.item.ostypename }}
</router-link><br>
{{ $t('label.backup') }} :
<router-link :to="{ path: '/guestos/' + dataPreFill.ostypeid }">
{{ dataPreFill.ostypename }}
</router-link>
</div>
</template>
</a-alert>
<div v-if="tabKey === 'templateid'"> <div v-if="tabKey === 'templateid'">
{{ $t('message.template.desc') }} {{ $t('message.template.desc') }}
<div v-if="isZoneSelectedMultiArch" style="width: 100%; margin-top: 5px"> <div v-if="isZoneSelectedMultiArch" style="width: 100%; margin-top: 5px">
@ -933,6 +950,7 @@ export default {
dataPreFill: {}, dataPreFill: {},
showDetails: false, showDetails: false,
showRootDiskSizeChanger: false, showRootDiskSizeChanger: false,
showOsTypeWarning: false,
showOverrideDiskOfferingOption: false, showOverrideDiskOfferingOption: false,
securitygroupids: [], securitygroupids: [],
rootDiskSizeFixed: 0, rootDiskSizeFixed: 0,
@ -978,6 +996,15 @@ export default {
isNormalUserOrProject () { isNormalUserOrProject () {
return ['User'].includes(this.$store.getters.userInfo.roletype) || store.getters.project.id return ['User'].includes(this.$store.getters.userInfo.roletype) || store.getters.project.id
}, },
selectedTemplateIso () {
if (this.tabKey === 'templateid' && this.template?.ostypeid) {
return { label: this.$t('label.template'), item: this.template, message: this.$t('message.template.ostype.different.from.backup') }
}
if (this.tabKey === 'isoid' && this.iso?.ostypeid) {
return { label: this.$t('label.iso'), item: this.iso, message: this.$t('message.iso.ostype.different.from.backup') }
}
return null
},
diskSize () { diskSize () {
const customRootDiskSize = _.get(this.instanceConfig, 'rootdisksize', null) const customRootDiskSize = _.get(this.instanceConfig, 'rootdisksize', null)
const customDataDiskSize = _.get(this.instanceConfig, 'size', null) const customDataDiskSize = _.get(this.instanceConfig, 'size', null)
@ -1687,6 +1714,7 @@ export default {
this.form.iothreadsenabled = template.details && Object.prototype.hasOwnProperty.call(template.details, 'iothreads') this.form.iothreadsenabled = template.details && Object.prototype.hasOwnProperty.call(template.details, 'iothreads')
this.form.iodriverpolicy = template.details?.['io.policy'] this.form.iodriverpolicy = template.details?.['io.policy']
this.form.keyboard = template.details?.keyboard this.form.keyboard = template.details?.keyboard
this.showOsTypeWarning = this.dataPreFill.ostypeid && template.ostypeid !== this.dataPreFill.ostypeid
if (template.details['vmware-to-kvm-mac-addresses']) { if (template.details['vmware-to-kvm-mac-addresses']) {
this.dataPreFill.macAddressArray = JSON.parse(template.details['vmware-to-kvm-mac-addresses']) this.dataPreFill.macAddressArray = JSON.parse(template.details['vmware-to-kvm-mac-addresses'])
} }
@ -1700,6 +1728,13 @@ export default {
this.tabKey = 'isoid' this.tabKey = 'isoid'
this.form.isoid = value this.form.isoid = value
this.form.templateid = null this.form.templateid = null
for (const key in this.options.isos) {
var iso = _.find(_.get(this.options.isos[key], 'iso', []), (option) => option.id === value)
if (iso) {
this.showOsTypeWarning = this.dataPreFill.ostypeid && iso.ostypeid !== this.dataPreFill.ostypeid
break
}
}
} else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) { } else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) {
this.vm[name] = value this.vm[name] = value
this.form[name] = value this.form[name] = value

View File

@ -29,7 +29,7 @@
<a-form-item :label="$t('label.name.optional')" name="name"> <a-form-item :label="$t('label.name.optional')" name="name">
<a-input v-model:value="form.name" /> <a-input v-model:value="form.name" />
</a-form-item> </a-form-item>
<a-form-item v-if="!resource.virtualmachineid" name="preserveIpAddresses" style="margin-top: 8px"> <a-form-item v-if="resource.isbackupvmexpunged" name="preserveIpAddresses" style="margin-top: 8px">
<a-switch v-model:checked="form.preserveIpAddresses" /> <a-switch v-model:checked="form.preserveIpAddresses" />
<template #label> <template #label>
<tooltip-label :title="$t('label.use.backup.ip.address')" :tooltip="$t('label.use.backup.ip.address.tooltip')"/> <tooltip-label :title="$t('label.use.backup.ip.address')" :tooltip="$t('label.use.backup.ip.address.tooltip')"/>
@ -91,16 +91,16 @@ export default {
required: true required: true
} }
}, },
created () { async created () {
this.fetchServiceOffering() await Promise.all[(
this.fetchBackupOffering().then(() => { this.fetchServiceOffering(),
this.fetchBackupRepository() this.fetchBackupOffering()
this.loading = false )]
}) this.loading = false
}, },
methods: { methods: {
fetchServiceOffering () { fetchServiceOffering () {
getAPI('listServiceOfferings', { return getAPI('listServiceOfferings', {
zoneid: this.resource.zoneid, zoneid: this.resource.zoneid,
id: this.resource.vmdetails.serviceofferingid, id: this.resource.vmdetails.serviceofferingid,
listall: true listall: true
@ -118,28 +118,19 @@ export default {
this.backupOffering = backupOfferings[0] this.backupOffering = backupOfferings[0]
}) })
}, },
fetchBackupRepository () {
if (this.backupOffering.provider !== 'nas') {
return
}
getAPI('listBackupRepositories', {
id: this.backupOffering.externalid
}).then(response => {
const backupRepositories = response.listbackuprepositoriesresponse.backuprepository || []
this.backupRepository = backupRepositories[0]
})
},
populatePreFillData () { populatePreFillData () {
this.vmdetails = this.resource.vmdetails this.vmdetails = this.resource.vmdetails
this.dataPreFill.zoneid = this.resource.zoneid this.dataPreFill.zoneid = this.resource.zoneid
this.dataPreFill.crosszoneinstancecreation = this.backupRepository?.crosszoneinstancecreation || this.backupOffering.provider === 'dummy' this.dataPreFill.crosszoneinstancecreation = this.backupOffering?.crosszoneinstancecreation || this.backupOffering.provider === 'dummy'
this.dataPreFill.isIso = (this.vmdetails.isiso === 'true') this.dataPreFill.isIso = (this.vmdetails.isiso === 'true')
this.dataPreFill.ostypeid = this.resource.vmdetails.ostypeid
this.dataPreFill.ostypename = this.resource.vmdetails.osname
this.dataPreFill.backupid = this.resource.id this.dataPreFill.backupid = this.resource.id
this.dataPreFill.computeofferingid = this.vmdetails.serviceofferingid this.dataPreFill.computeofferingid = this.vmdetails.serviceofferingid
this.dataPreFill.templateid = this.vmdetails.templateid this.dataPreFill.templateid = this.vmdetails.templateid
this.dataPreFill.allowtemplateisoselection = true this.dataPreFill.allowtemplateisoselection = true
this.dataPreFill.isoid = this.vmdetails.templateid this.dataPreFill.isoid = this.vmdetails.templateid
this.dataPreFill.allowIpAddressesFetch = !this.resource.virtualmachineid this.dataPreFill.allowIpAddressesFetch = this.resource.isbackupvmexpunged
if (this.vmdetails.nics) { if (this.vmdetails.nics) {
const nics = JSON.parse(this.vmdetails.nics) const nics = JSON.parse(this.vmdetails.nics)
this.dataPreFill.networkids = nics.map(nic => nic.networkid) this.dataPreFill.networkids = nics.map(nic => nic.networkid)