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_TOTAL = "backupstoragetotal";
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 BASE64_IMAGE = "base64image";
public static final String BGP_PEERS = "bgppeers";

View File

@ -61,6 +61,10 @@ public class BackupOfferingResponse extends BaseResponse {
@Param(description = "zone name")
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)
@Param(description = "the date this backup offering was created")
private Date created;
@ -97,6 +101,10 @@ public class BackupOfferingResponse extends BaseResponse {
this.zoneName = zoneName;
}
public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) {
this.crossZoneInstanceCreation = crossZoneInstanceCreation;
}
public void setCreated(Date 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")
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() {
return id;
}
@ -306,4 +310,8 @@ public class BackupResponse extends BaseResponse {
public void setVmOfferingRemoved(Boolean 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;
public interface BackupOfferingDao extends GenericDao<BackupOfferingVO, Long> {
BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy);
BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy, Boolean crossZoneInstanceCreation);
BackupOffering findByExternalId(String externalId, Long zoneId);
BackupOffering findByName(String name, Long zoneId);
}

View File

@ -50,7 +50,7 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
}
@Override
public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) {
public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, Boolean crossZoneInstanceCreation) {
DataCenterVO zone = dataCenterDao.findById(offering.getZoneId());
BackupOfferingResponse response = new BackupOfferingResponse();
@ -64,6 +64,9 @@ public class BackupOfferingDaoImpl extends GenericDaoBase<BackupOfferingVO, Long
response.setZoneId(zone.getUuid());
response.setZoneName(zone.getName());
}
if (crossZoneInstanceCreation) {
response.setCrossZoneInstanceCreation(true);
}
response.setCreated(offering.getCreated());
response.setObjectName("backupoffering");
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.ZoneResponse;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.BackupRepository;
import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupDao;
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.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -493,6 +495,7 @@ public class ApiDBUtils {
static BackupDao s_backupDao;
static BackupScheduleDao s_backupScheduleDao;
static BackupOfferingDao s_backupOfferingDao;
static BackupRepositoryDao s_backupRepositoryDao;
static NicDao s_nicDao;
static ResourceManagerUtil s_resourceManagerUtil;
static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
@ -751,6 +754,8 @@ public class ApiDBUtils {
@Inject
private BackupOfferingDao backupOfferingDao;
@Inject
private BackupRepositoryDao backupRepositoryDao;
@Inject
private BackupScheduleDao backupScheduleDao;
@Inject
private NicDao nicDao;
@ -899,6 +904,7 @@ public class ApiDBUtils {
s_backupDao = backupDao;
s_backupScheduleDao = backupScheduleDao;
s_backupOfferingDao = backupOfferingDao;
s_backupRepositoryDao = backupRepositoryDao;
s_resourceIconDao = resourceIconDao;
s_resourceManagerUtil = resourceManagerUtil;
s_objectStoreDao = objectStoreDao;
@ -2297,8 +2303,10 @@ public class ApiDBUtils {
return s_backupScheduleDao.newBackupScheduleResponse(schedule);
}
public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy) {
return s_backupOfferingDao.newBackupOfferingResponse(policy);
public static BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) {
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) {

View File

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

View File

@ -9606,11 +9606,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} else {
String serviceOfferingUuid = backup.getDetail(ApiConstants.SERVICE_OFFERING_ID);
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);
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);
@ -9625,11 +9625,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
} else {
String templateUuid = backup.getDetail(ApiConstants.TEMPLATE_ID);
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);
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());

View File

@ -122,12 +122,14 @@ import com.cloud.projects.Project;
import com.cloud.serializer.GsonHelper;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.template.VirtualMachineTemplate;
@ -232,6 +234,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
private ResourceLimitService resourceLimitMgr;
@Inject
private AlertManager alertManager;
@Inject
private GuestOSDao _guestOSDao;
private AsyncJobDispatcher asyncJobDispatcher;
private Timer backupTimer;
@ -379,7 +383,15 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
ServiceOffering serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
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());
HashMap<String, String> settings = new HashMap<>();
@ -2143,7 +2155,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
if (details.containsKey(ApiConstants.TEMPLATE_ID)) {
VirtualMachineTemplate template = vmTemplateDao.findByUuid(details.get(ApiConstants.TEMPLATE_ID));
if (template != null) {
details.put(ApiConstants.TEMPLATE_ID, template.getUuid());
details.put(ApiConstants.TEMPLATE_NAME, template.getName());
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)) {
ServiceOffering serviceOffering = serviceOfferingDao.findByUuid(details.get(ApiConstants.SERVICE_OFFERING_ID));
if (serviceOffering != null) {
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
details.put(ApiConstants.SERVICE_OFFERING_NAME, serviceOffering.getName());
}
}
@ -2186,10 +2196,15 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
response.setId(backup.getUuid());
response.setName(backup.getName());
response.setDescription(backup.getDescription());
response.setVmName(vm.getHostName());
response.setVmId(vm.getUuid());
if (vm.getBackupOfferingId() == null || vm.getBackupOfferingId() != backup.getBackupOfferingId()) {
response.setVmOfferingRemoved(true);
if (vm != null) {
response.setVmName(vm.getHostName());
response.setVmId(vm.getUuid());
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.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
String backedUpVolumes;
String backedUpVolumes = "";
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 {
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)) {
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());
vmDetails.putAll(details);
response.setVmDetails(vmDetails);

View File

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

View File

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

View File

@ -168,6 +168,23 @@
:tabList="tabList"
:activeTabKey="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'">
{{ $t('message.template.desc') }}
<div v-if="isZoneSelectedMultiArch" style="width: 100%; margin-top: 5px">
@ -933,6 +950,7 @@ export default {
dataPreFill: {},
showDetails: false,
showRootDiskSizeChanger: false,
showOsTypeWarning: false,
showOverrideDiskOfferingOption: false,
securitygroupids: [],
rootDiskSizeFixed: 0,
@ -978,6 +996,15 @@ export default {
isNormalUserOrProject () {
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 () {
const customRootDiskSize = _.get(this.instanceConfig, 'rootdisksize', 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.iodriverpolicy = template.details?.['io.policy']
this.form.keyboard = template.details?.keyboard
this.showOsTypeWarning = this.dataPreFill.ostypeid && template.ostypeid !== this.dataPreFill.ostypeid
if (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.form.isoid = value
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)) {
this.vm[name] = value
this.form[name] = value

View File

@ -29,7 +29,7 @@
<a-form-item :label="$t('label.name.optional')" name="name">
<a-input v-model:value="form.name" />
</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" />
<template #label>
<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
}
},
created () {
this.fetchServiceOffering()
this.fetchBackupOffering().then(() => {
this.fetchBackupRepository()
this.loading = false
})
async created () {
await Promise.all[(
this.fetchServiceOffering(),
this.fetchBackupOffering()
)]
this.loading = false
},
methods: {
fetchServiceOffering () {
getAPI('listServiceOfferings', {
return getAPI('listServiceOfferings', {
zoneid: this.resource.zoneid,
id: this.resource.vmdetails.serviceofferingid,
listall: true
@ -118,28 +118,19 @@ export default {
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 () {
this.vmdetails = this.resource.vmdetails
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.ostypeid = this.resource.vmdetails.ostypeid
this.dataPreFill.ostypename = this.resource.vmdetails.osname
this.dataPreFill.backupid = this.resource.id
this.dataPreFill.computeofferingid = this.vmdetails.serviceofferingid
this.dataPreFill.templateid = this.vmdetails.templateid
this.dataPreFill.allowtemplateisoselection = true
this.dataPreFill.isoid = this.vmdetails.templateid
this.dataPreFill.allowIpAddressesFetch = !this.resource.virtualmachineid
this.dataPreFill.allowIpAddressesFetch = this.resource.isbackupvmexpunged
if (this.vmdetails.nics) {
const nics = JSON.parse(this.vmdetails.nics)
this.dataPreFill.networkids = nics.map(nic => nic.networkid)