Skip to content

Commit 8ba1436

Browse files
committed
Show backup instance metadata as a separate tab
1 parent cb7123f commit 8ba1436

File tree

8 files changed

+207
-50
lines changed

8 files changed

+207
-50
lines changed

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ public class ApiConstants {
371371
public static final String NETMASK = "netmask";
372372
public static final String NEW_NAME = "newname";
373373
public static final String NIC = "nic";
374+
public static final String NICS = "nics";
374375
public static final String NIC_NETWORK_LIST = "nicnetworklist";
375376
public static final String NIC_IP_ADDRESS_LIST = "nicipaddresslist";
376377
public static final String NIC_MULTIQUEUE_NUMBER = "nicmultiqueuenumber";
@@ -486,6 +487,7 @@ public class ApiConstants {
486487
public static final String SERIAL = "serial";
487488
public static final String SERVICE_IP = "serviceip";
488489
public static final String SERVICE_OFFERING_ID = "serviceofferingid";
490+
public static final String SERVICE_OFFERING_NAME = "serviceofferingname";
489491
public static final String SESSIONKEY = "sessionkey";
490492
public static final String SHOW_CAPACITIES = "showcapacities";
491493
public static final String SHOW_REMOVED = "showremoved";

engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717

1818
package org.apache.cloudstack.backup.dao;
1919

20+
import java.lang.reflect.Type;
2021
import java.util.ArrayList;
22+
import java.util.HashMap;
2123
import java.util.List;
2224
import java.util.Map;
2325
import java.util.Objects;
2426

2527
import javax.annotation.PostConstruct;
2628
import javax.inject.Inject;
2729

30+
import com.cloud.offering.ServiceOffering;
31+
import com.cloud.service.dao.ServiceOfferingDao;
2832
import com.cloud.storage.Storage;
2933
import com.cloud.storage.dao.VMTemplateDao;
3034
import com.cloud.template.VirtualMachineTemplate;
@@ -52,7 +56,10 @@
5256
import com.cloud.vm.VMInstanceVO;
5357
import com.cloud.vm.VirtualMachine;
5458
import com.cloud.vm.dao.VMInstanceDao;
59+
import com.cloud.network.Network;
60+
import com.cloud.network.dao.NetworkDao;
5561
import com.google.gson.Gson;
62+
import com.google.gson.reflect.TypeToken;
5663

5764
public class BackupDaoImpl extends GenericDaoBase<BackupVO, Long> implements BackupDao {
5865

@@ -77,6 +84,12 @@ public class BackupDaoImpl extends GenericDaoBase<BackupVO, Long> implements Bac
7784
@Inject
7885
BackupDetailsDao backupDetailsDao;
7986

87+
@Inject
88+
ServiceOfferingDao serviceOfferingDao;
89+
90+
@Inject
91+
NetworkDao networkDao;
92+
8093
private SearchBuilder<BackupVO> backupSearch;
8194
private GenericSearchBuilder<BackupVO, Long> CountBackupsByAccount;
8295
private GenericSearchBuilder<BackupVO, SumCount> CalculateBackupStorageByAccount;
@@ -286,6 +299,36 @@ public List<Long> listVmIdsWithBackupsInZone(Long zoneId) {
286299
return customSearchIncludingRemoved(sc, null);
287300
}
288301

302+
Map<String, String> getDetailsFromBackupDetails(Long backupId) {
303+
Map<String, String> details = backupDetailsDao.listDetailsKeyPairs(backupId, true);
304+
if (details == null) {
305+
return null;
306+
}
307+
if (details.containsKey(ApiConstants.SERVICE_OFFERING_ID)) {
308+
ServiceOffering serviceOffering = serviceOfferingDao.findByUuid(details.get(ApiConstants.SERVICE_OFFERING_ID));
309+
if (serviceOffering != null) {
310+
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
311+
details.put(ApiConstants.SERVICE_OFFERING_NAME, serviceOffering.getName());
312+
}
313+
}
314+
if (details.containsKey(ApiConstants.NICS)) {
315+
Type type = new TypeToken<List<Map<String, String>>>() {}.getType();
316+
List<Map<String, String>> nics = new Gson().fromJson(details.get(ApiConstants.NICS), type);
317+
318+
for (Map<String, String> nic : nics) {
319+
String networkUuid = nic.get(ApiConstants.NETWORK_ID);
320+
if (networkUuid != null) {
321+
Network network = networkDao.findByUuid(networkUuid);
322+
if (network != null) {
323+
nic.put(ApiConstants.NETWORK_NAME, network.getName());
324+
}
325+
}
326+
}
327+
details.put(ApiConstants.NICS, new Gson().toJson(nics));
328+
}
329+
return details;
330+
}
331+
289332
@Override
290333
public BackupResponse newBackupResponse(Backup backup, Boolean listVmDetails) {
291334
VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
@@ -333,16 +376,18 @@ public BackupResponse newBackupResponse(Backup backup, Boolean listVmDetails) {
333376
response.setZone(zone.getName());
334377

335378
if (Boolean.TRUE.equals(listVmDetails)) {
336-
Map<String, String> details = backupDetailsDao.listDetailsKeyPairs(backup.getId(), true);
337-
details.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString());
379+
Map<String, String> vmDetails = new HashMap<>();
380+
vmDetails.put(ApiConstants.HYPERVISOR, vm.getHypervisorType().toString());
338381
VirtualMachineTemplate template = templateDao.findById(vm.getTemplateId());
339382
if (template != null) {
340-
details.put(ApiConstants.TEMPLATE_ID, template.getUuid());
341-
details.put(ApiConstants.IS_ISO, String.valueOf(template.getFormat().equals(Storage.ImageFormat.ISO)));
342-
}
343-
if (details != null) {
344-
response.setVmDetails(details);
383+
vmDetails.put(ApiConstants.TEMPLATE_ID, template.getUuid());
384+
vmDetails.put(ApiConstants.TEMPLATE_NAME, template.getName());
385+
vmDetails.put(ApiConstants.IS_ISO, String.valueOf(template.getFormat().equals(Storage.ImageFormat.ISO)));
345386
}
387+
388+
Map<String, String> details = getDetailsFromBackupDetails(backup.getId());
389+
vmDetails.putAll(details);
390+
response.setVmDetails(vmDetails);
346391
}
347392

348393
response.setObjectName("backup");

server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import java.util.Collections;
2222
import java.util.Date;
2323
import java.util.HashMap;
24+
import java.util.HashSet;
2425
import java.util.LinkedHashMap;
25-
import java.util.LinkedHashSet;
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Objects;
@@ -329,22 +329,26 @@ public Map<String, String> getVmDetailsForBackup(VirtualMachine vm) {
329329
ServiceOffering serviceOffering = serviceOfferingDao.findById(vm.getServiceOfferingId());
330330
details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOffering.getUuid());
331331
List<UserVmJoinVO> userVmJoinVOs = userVmJoinDao.searchByIds(vm.getId());
332+
332333
if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) {
333-
Set<String> networkIds = new LinkedHashSet<>();
334-
Set<String> ipAddresses = new LinkedHashSet<>();
335-
Set<String> ip6Addresses = new LinkedHashSet<>();
336-
Set<String> macAddresses = new LinkedHashSet<>();
334+
List<Map<String, String>> nics = new ArrayList<>();
335+
Set<String> seen = new HashSet<>();
336+
337337
for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) {
338-
networkIds.add(userVmJoinVO.getNetworkUuid());
339-
ipAddresses.add(userVmJoinVO.getIpAddress());
340-
ip6Addresses.add(userVmJoinVO.getIp6Address());
341-
macAddresses.add(userVmJoinVO.getMacAddress());
338+
Map<String, String> nicInfo = new HashMap<>();
339+
String key = userVmJoinVO.getNetworkUuid();
340+
if (seen.add(key)) {
341+
nicInfo.put(ApiConstants.NETWORK_ID, userVmJoinVO.getNetworkUuid());
342+
nicInfo.put(ApiConstants.IP_ADDRESS, userVmJoinVO.getIpAddress());
343+
nicInfo.put(ApiConstants.IP6_ADDRESS, userVmJoinVO.getIp6Address());
344+
nicInfo.put(ApiConstants.MAC_ADDRESS, userVmJoinVO.getMacAddress());
345+
nics.add(nicInfo);
346+
}
342347
}
343-
if (!networkIds.isEmpty()) {
344-
details.put(ApiConstants.NETWORK_IDS, String.join(",", networkIds));
345-
details.put(ApiConstants.IP_ADDRESSES, String.join(",", ipAddresses));
346-
details.put(ApiConstants.IP6_ADDRESSES, String.join(",", ip6Addresses));
347-
details.put(ApiConstants.MAC_ADDRESSES, String.join(",", macAddresses));
348+
349+
if (!nics.isEmpty()) {
350+
String nicJson = new Gson().toJson(nics);
351+
details.put("nics", nicJson);
348352
}
349353
}
350354
return details;
@@ -1092,47 +1096,39 @@ public Map<Long, Network.IpAddresses> getIpToNetworkMapFromBackup(Backup backup,
10921096
{
10931097
Map<Long, Network.IpAddresses> ipToNetworkMap = new LinkedHashMap<Long, Network.IpAddresses>();
10941098

1095-
String networkIdsString = backup.getDetail(ApiConstants.NETWORK_IDS);
1096-
if (networkIdsString == null) {
1099+
String nicsJson = backup.getDetail(ApiConstants.NICS);
1100+
if (nicsJson == null) {
10971101
throw new CloudRuntimeException("Backup doesn't contain network information. " +
10981102
"Please specify at least one valid network while creating instance");
10991103
}
1100-
List<String> networkUuids = List.of(networkIdsString.split(","));
11011104

1102-
List<String> requestedIp = null;
1103-
List<String> requestedMac = null;
1104-
List<String> requestedIpv6 = null;
1105-
boolean preserveIpAddresses = false;
1106-
if (preserveIps) {
1107-
VMInstanceVO vm = vmInstanceDao.findById(backup.getVmId());
1108-
if (vm == null) {
1109-
preserveIpAddresses = true;
1105+
List<Map<String, String>> nics = new Gson().fromJson(nicsJson, List.class);
1106+
1107+
for (Map<String, String> nic : nics) {
1108+
String networkUuid = nic.get(ApiConstants.NETWORK_ID);
1109+
if (networkUuid == null) {
1110+
throw new CloudRuntimeException("Backup doesn't contain network information. " +
1111+
"Please specify at least one valid network while creating instance");
11101112
}
1111-
}
1112-
if (preserveIpAddresses) {
1113-
requestedIp = parseAddressString(backup.getDetail(ApiConstants.IP_ADDRESSES));
1114-
requestedMac = parseAddressString(backup.getDetail(ApiConstants.MAC_ADDRESSES));
1115-
requestedIpv6 = parseAddressString(backup.getDetail(ApiConstants.IP6_ADDRESSES));
1116-
}
11171113

1118-
int index = 0;
1119-
for (String networkUuid: networkUuids) {
11201114
Network network = networkDao.findByUuid(networkUuid);
11211115
if (network == null) {
1122-
throw new CloudRuntimeException("Unable to find network with the uuid " + networkUuid + "stored in backup. " +
1116+
throw new CloudRuntimeException("Unable to find network with the uuid " + networkUuid + " stored in backup. " +
11231117
"Please specify a valid network id while creating the instance");
11241118
}
1119+
11251120
Long networkId = network.getId();
11261121
Network.IpAddresses ipAddresses = null;
1127-
if (preserveIpAddresses) {
1128-
String ip = (requestedIp != null) ? requestedIp.get(index) : null;
1129-
String ipv6 = (requestedIpv6 != null) ? requestedIpv6.get(index) : null;
1130-
String mac = (requestedMac != null) ? requestedMac.get(index) : null;
1122+
1123+
if (preserveIps) {
1124+
String ip = nic.get(ApiConstants.IP_ADDRESS);
1125+
String ipv6 = nic.get(ApiConstants.IP6_ADDRESS);
1126+
String mac = nic.get(ApiConstants.MAC_ADDRESS);
11311127
ipAddresses = networkService.getIpAddressesFromIps(ip, ipv6, mac);
11321128
}
1129+
11331130
ipToNetworkMap.put(networkId, ipAddresses);
11341131
networkIds.add(networkId);
1135-
index += 1;
11361132
}
11371133
return ipToNetworkMap;
11381134
}

ui/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@
11911191
"label.instance": "Instance",
11921192
"label.instance.conversion.support": "Instance Conversion Supported",
11931193
"label.instance.groups": "Instance groups",
1194+
"label.instance.metadata": "Instance metadata",
11941195
"label.instance.name": "Instance name",
11951196
"label.instancename": "Internal name",
11961197
"label.instanceport": "Instance port",
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<template>
2+
<a-list
3+
size="small"
4+
:dataSource="fetchBackupMetadataFields()">
5+
<template #renderItem="{item}">
6+
<a-list-item v-if="backupMetadata[item] !== undefined">
7+
<div style="width: 100%">
8+
<strong>{{ getFieldLabel(item) }}</strong>
9+
<br/>
10+
<div v-if="item === 'nics'">
11+
<div v-for="(nic, idx) in getNicEntities()" :key="idx">
12+
<router-link :to="{ path: '/guestnetwork/' + nic.networkid }">
13+
{{ nic.networkname || nic.networkid }}
14+
</router-link>
15+
<br/>
16+
IP Address: {{ nic.ipaddress }}
17+
<span v-if="nic.ip6address && nic.ip6address !== 'null'"> | IPv6: {{ nic.ip6address }}</span>
18+
<br/>
19+
MAC Address: {{ nic.macaddress }}
20+
<div v-if="idx < getNicEntities().length - 1" style="margin: 6px 0; border-bottom: 1px dashed #d9d9d9;"></div>
21+
</div>
22+
</div>
23+
<div v-else-if="item === 'templateid'">
24+
<router-link :to="{ path: '/' + (backupMetadata.isiso === 'true' ? 'iso' : 'template') + '/' + backupMetadata[item] }">
25+
{{ getTemplateDisplayName() }}
26+
</router-link>
27+
</div>
28+
<div v-else-if="item === 'serviceofferingid'">
29+
<router-link :to="{ path: '/computeoffering/' + backupMetadata[item] }">
30+
{{ getServiceOfferingDisplayName() }}
31+
</router-link>
32+
</div>
33+
<div v-else>{{ backupMetadata[item] }}</div>
34+
</div>
35+
</a-list-item>
36+
</template>
37+
</a-list>
38+
</template>
39+
40+
<script>
41+
42+
export default {
43+
name: 'BackupMetadata',
44+
data () {
45+
return {
46+
backupMetadata: this.resource.vmdetails,
47+
templateName: '',
48+
isoName: ''
49+
}
50+
},
51+
props: {
52+
resource: {
53+
type: Object,
54+
required: true
55+
}
56+
},
57+
methods: {
58+
fetchBackupMetadataFields () {
59+
if (!this.backupMetadata || Object.keys(this.backupMetadata).length === 0) {
60+
return []
61+
}
62+
const fieldOrder = []
63+
fieldOrder.push('templateid')
64+
if (this.backupMetadata.isiso === 'true') {
65+
fieldOrder.push('hypervisor')
66+
}
67+
fieldOrder.push('serviceofferingid')
68+
fieldOrder.push('nics')
69+
70+
return fieldOrder.filter(field => this.backupMetadata[field] !== undefined)
71+
},
72+
getNicEntities () {
73+
if (this.backupMetadata.nics) {
74+
const nics = JSON.parse(this.backupMetadata.nics)
75+
return nics
76+
}
77+
return []
78+
},
79+
getFieldLabel (field) {
80+
if (field === 'templateid') {
81+
return this.backupMetadata.isiso === 'true' ? this.$t('label.iso') : this.$t('label.template')
82+
}
83+
return this.$t('label.' + String(field).toLowerCase())
84+
},
85+
getTemplateDisplayName () {
86+
if (this.backupMetadata.templatename) {
87+
return this.backupMetadata.templatename
88+
}
89+
return this.backupMetadata.isiso === 'true' ? this.isoName : this.templateName
90+
},
91+
getServiceOfferingDisplayName () {
92+
if (this.backupMetadata.serviceofferingname) {
93+
return this.backupMetadata.serviceofferingname
94+
}
95+
return this.backupMetadata.serviceofferingid
96+
}
97+
}
98+
}
99+
</script>

ui/src/config/section/storage.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,23 @@ export default {
418418
title: 'label.backup',
419419
icon: 'cloud-upload-outlined',
420420
permission: ['listBackups'],
421+
params: { listvmdetails: 'true' },
421422
columns: ['name', 'status', 'size', 'virtualsize', 'virtualmachinename', 'backupofferingname', 'intervaltype', 'type', 'created', 'account', 'domain', 'zone'],
422423
details: ['name', 'description', 'virtualmachinename', 'id', 'intervaltype', 'type', 'externalid', 'size', 'virtualsize', 'volumes', 'backupofferingname', 'zone', 'account', 'domain', 'created'],
423424
searchFilters: () => {
424425
var filters = ['name', 'zoneid', 'domainid', 'account', 'backupofferingid']
425426
return filters
426427
},
428+
tabs: [
429+
{
430+
name: 'details',
431+
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
432+
},
433+
{
434+
name: 'instance.metadata',
435+
component: shallowRef(defineAsyncComponent(() => import('@/components/view/BackupMetadata.vue')))
436+
}
437+
],
427438
actions: [
428439
{
429440
api: 'restoreBackup',

ui/src/views/compute/InstanceTab.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
apiName="listBackups"
6161
:resource="resource"
6262
:params="{virtualmachineid: dataResource.id}"
63-
:columns="['name', 'status', 'size', 'virtualsize', 'type', 'created']"
63+
:columns="['name', 'status', 'size', 'virtualsize', 'type', 'intervaltype', 'created']"
6464
:routerlinks="(record) => { return { name: '/backup/' + record.id } }"
6565
:showSearch="false"/>
6666
</a-tab-pane>

ui/src/views/storage/CreateVMFromBackup.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ export default {
127127
this.dataPreFill.templateid = this.vmdetails.templateid
128128
this.dataPreFill.isoid = this.vmdetails.templateid
129129
this.dataPreFill.allowIpAddressesFetch = !this.resource.virtualmachineid
130-
this.dataPreFill.networkids = (this.vmdetails.networkids || '').split(',')
131-
this.dataPreFill.ipAddresses = (this.vmdetails.ipaddresses || '').split(',')
132-
this.dataPreFill.macAddresses = (this.vmdetails.macaddresses || '').split(',')
130+
if (this.vmdetails.nics) {
131+
const nics = JSON.parse(this.vmdetails.nics)
132+
this.dataPreFill.networkids = nics.map(nic => nic.networkid)
133+
this.dataPreFill.ipAddresses = nics.map(nic => nic.ipaddress)
134+
this.dataPreFill.macAddresses = nics.map(nic => nic.macaddress)
135+
}
133136
const volumes = JSON.parse(this.resource.volumes)
134137
const disksdetails = volumes.map((volume, index) => ({
135138
name: volume.path,

0 commit comments

Comments
 (0)